diff --git a/.env b/.env index 8670f7e..5841caf 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -SERVER_NAME='mc.megufun.net' +CF_API_KEY=d40ea4a0-3430-4501-b95d-83af9cdc9cb5 +SERVER_NAME=mc.megufun.net diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2f9453f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Deploy CloudFormation Templates + +on: + workflow_run: + workflows: ["Static Code Tests"] + types: + - completed + branches: [main] + +permissions: + id-token: write + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Verify AWS credentials + run: | + aws sts get-caller-identity \ No newline at end of file diff --git a/.github/workflows/static-code-test.yml b/.github/workflows/static-code-test.yml new file mode 100644 index 0000000..de06f82 --- /dev/null +++ b/.github/workflows/static-code-test.yml @@ -0,0 +1,61 @@ +--- +name: Static Code Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + lint: + name: Lint Code Base + runs-on: ubuntu-latest + # trunk-ignore(checkov/CKV2_GHA_1) + permissions: + contents: read + statuses: write + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + with: + # Full git history is needed to get a proper list of changed files + fetch-depth: 0 + + + - name: Super-Linter + uses: super-linter/super-linter@v5 + + env: + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: ${{ github.event.pull_request.base.ref }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Set base path for all config files + LINTER_RULES_PATH: .trunk/config + + + # Match trunk.yaml linters + VALIDATE_PYTHON_BANDIT: true + VALIDATE_PYTHON_BLACK: true + VALIDATE_YAML_CFNLINT: true + VALIDATE_CHECKOV: true + VALIDATE_ENV: true + VALIDATE_MARKDOWN: true + VALIDATE_PYTHON_ISORT: true + VALIDATE_PRETTIER: true + VALIDATE_PYTHON_RUFF: true + VALIDATE_SHELL_SHFMT: true + VALIDATE_YAML: true + + # Linter configs to match trunk + # PYTHON_BLACK_CONFIG_FILE: pyproject.toml + # PYTHON_ISORT_CONFIG_FILE: pyproject.toml + # PYTHON_RUFF_CONFIG_FILE: pyproject.toml + + # Python version to match trunk runtime + PYTHON_VERSION: 3.10.8 + NODE_VERSION: 18.20.5 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9ff546b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Ignore all YAML files +*.yml +*.yaml diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 0000000..15966d0 --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,9 @@ +*out +*logs +*actions +*notifications +*tools +plugins +user_trunk.yaml +user.yaml +tmp diff --git a/.trunk/configs/.isort.cfg b/.trunk/configs/.isort.cfg new file mode 100644 index 0000000..b9fb3f3 --- /dev/null +++ b/.trunk/configs/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile=black diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml new file mode 100644 index 0000000..b40ee9d --- /dev/null +++ b/.trunk/configs/.markdownlint.yaml @@ -0,0 +1,2 @@ +# Prettier friendly markdownlint config (all formatting rules disabled) +extends: markdownlint/style/prettier diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc new file mode 100644 index 0000000..8c7b1ad --- /dev/null +++ b/.trunk/configs/.shellcheckrc @@ -0,0 +1,7 @@ +enable=all +source-path=SCRIPTDIR +disable=SC2154 + +# If you're having issues with shellcheck following source, disable the errors via: +# disable=SC1090 +# disable=SC1091 diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml new file mode 100644 index 0000000..b65e009 --- /dev/null +++ b/.trunk/configs/.yamllint.yaml @@ -0,0 +1,12 @@ +rules: + quoted-strings: + extra-allowed: ["{|}"] + quote-type: "any" + required: true + key-duplicates: {} + octal-values: + forbid-implicit-octal: true + line-length: + max: 100 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: false diff --git a/.trunk/configs/ruff.toml b/.trunk/configs/ruff.toml new file mode 100644 index 0000000..f5a235c --- /dev/null +++ b/.trunk/configs/ruff.toml @@ -0,0 +1,5 @@ +# Generic, formatter-friendly config. +select = ["B", "D3", "E", "F"] + +# Never enforce `E501` (line length violations). This should be handled by formatters. +ignore = ["E501"] diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 0000000..aa0b524 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,43 @@ +# This file controls the behavior of Trunk: https://docs.trunk.io/cli +# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml +version: 0.1 +cli: + version: 1.22.10 +# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) +plugins: + sources: + - id: trunk + ref: v1.6.7 + uri: https://github.com/trunk-io/plugins +# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) +runtimes: + enabled: + - go@1.21.0 + - node@18.20.5 + - python@3.10.8 +# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) +lint: + disabled: + - oxipng + enabled: + - bandit@1.8.3 + - black@25.1.0 + - cfnlint@1.25.1 + - checkov@3.2.372 + - dotenv-linter@3.3.0 + - git-diff-check + - isort@6.0.0 + - markdownlint@0.44.0 + - prettier@3.5.1 + - ruff@0.9.6 + - shellcheck@0.10.0 + - shfmt@3.6.0 + - trufflehog@3.88.10 + - yamllint@1.35.1 +actions: + disabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + enabled: + - trunk-upgrade-available diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..80056ee --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,13 @@ +--- +rules: + quoted-strings: + extra-allowed: ["{|}"] + quote-type: "any" + required: true + key-duplicates: {} + octal-values: + forbid-implicit-octal: true + line-length: + max: 100 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: false # comment diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f3482a --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +# GitHub Actions AWS Integration Setup + +This guide explains how to set up secure AWS authentication for GitHub Actions using OIDC (OpenID Connect). + +## Prerequisites + +- AWS CLI installed and configured +- Administrative access to your AWS account +- GitHub repository access + +## Deployment Steps + +### 1. Deploy the OIDC Connection Stack + +First, deploy the pre-GitHub connection CloudFormation stack that sets up OIDC authentication: + +```bash +aws cloudformation deploy \ + --template-file pre-github-connection.yml \ + --stack-name minecraft-github-oidc \ + --parameter-overrides \ + GitHubOrg= \ + RepositoryName= \ + --capabilities CAPABILITY_NAMED_IAM +``` + +Replace the following values: + +- : Your GitHub username or organization +- : Your repository name +- minecraft: Change if you want a different prefix for your resources + +### 2. Get the Role ARN + +After the stack is created, retrieve the Role ARN: + +```bash +aws cloudformation describe-stacks \ + --stack-name github-oidc-stack \ + --query 'Stacks[0].Outputs[?OutputKey==`RoleArn`].OutputValue' \ + --output text +``` + +### 3. Configure GitHub Repository + +1. Go to your GitHub repository +2. Navigate to Settings > Secrets and variables > Actions +3. Add the following secrets: + - Name: AWS_ROLE_ARN + - Value: (The Role ARN from step 2) + - Name: AWS_REGION + - Value: Your AWS region (e.g., us-east-1) + +### 4. Verify Setup + +Go to your GitHub repository's Actions tab +Run the workflow manually using the "Run workflow" button +Check that the workflow can successfully authenticate to AWS + +## Security Features + +The OIDC setup includes: + +- No long-term credentials stored in GitHub +- Temporary security credentials for each workflow run +- Resource name constraints using the specified prefix +- Permissions boundary to prevent privilege escalation +- Required resource tagging for created resources + +## Troubleshooting + +Common issues and solutions: + +1. Authentication Failures + + - Verify the Role ARN is correctly set in GitHub secrets + - Check that the GitHub repository name matches the configuration + - Ensure the workflow has permissions.id-token: write + +2. Permission Denied + + - Verify resources are tagged with Purpose: minecraft-\* + - Check resource names start with the specified prefix + - Review CloudWatch Logs for detailed error messages + +3. Stack Creation Failures + + - Ensure templates are valid using aws cloudformation validate-template + - Check if resources comply with the permissions boundary + - Verify all required parameters are provided + +## Maintenance + +- Regularly review and update the OIDC provider thumbprint +- Monitor CloudWatch Logs for unauthorized access attempts +- Update the permissions boundary as needed for new resource types diff --git a/aws/README.md b/aws/README.md new file mode 100644 index 0000000..e69de29 diff --git a/aws/cloudformation/ec2-minecraft-server.yml b/aws/cloudformation/ec2-minecraft-server.yml new file mode 100644 index 0000000..f92f301 --- /dev/null +++ b/aws/cloudformation/ec2-minecraft-server.yml @@ -0,0 +1,431 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Deploy configuration and handling of the Minecraft server' + +Parameters: + + ServerName: + Type: 'String' + Description: 'Name of the Minecraft server' + Default: 'minecraft-server' + + MinecraftBucket: + Type: String + Description: 'S3 bucket for Minecraft artifacts' + + MinecraftPort: + Type: Number + Description: 'Port number for Minecraft server' + Default: 25565 + MinValue: 1024 + MaxValue: 65535 + ConstraintDescription: 'Must be a valid port number between 1024 and 65535' + + InactivityShutdownMinutes: + Type: Number + Description: 'Minutes of inactivity before stopping the instance' + Default: 30 + MinValue: 5 + MaxValue: 1440 + + # TerminateAfterDays: + # Type: Number + # Description: 'Days of being stopped before terminating the instance' + # Default: 7 + # MinValue: 1 + # MaxValue: 30 + + InstanceType: + Type: String + Description: 'EC2 instance type' + Default: 't4g.small' + AllowedValues: + - 't4g.small' + - 't4g.medium' + - 't4g.large' + - 't4g.xlarge' + - 't4g.2xlarge' + + AMI: + Type: AWS::SSM::Parameter::Value + Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64' + Description: 'Amazon Linux 2023 ARM64 AMI ID' + +Resources: + # Security Groups + NetworkLoadBalancerSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: 'Security group for Load Balancer' + VpcId: + Fn::ImportValue: !Sub '${ServerName}-vpc-id' + SecurityGroupEgress: + - IpProtocol: -1 + CidrIp: '0.0.0.0/0' + Description: 'Allow all outbound traffic for health checks' + Tags: + - Key: Name + Value: !Sub '${ServerName}-NLB-SecurityGroup' + + MinecraftServerSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: 'Security group for Minecraft server' + VpcId: + Fn::ImportValue: !Sub '${ServerName}-vpc-id' + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + SourceSecurityGroupId: + Fn::ImportValue: !Sub '${ServerName}-ssm-security-group' + Description: 'Allow HTTPS inbound for SSM connection' + SecurityGroupEgress: + - IpProtocol: -1 + CidrIp: '0.0.0.0/0' + Description: 'Allow all outbound traffic' + Tags: + - Key: Name + Value: !Sub '${ServerName}-Minecraft-SecurityGroup' + + EFSSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: 'Security group for EFS mount target' + VpcId: + Fn::ImportValue: !Sub '${ServerName}-vpc-id' + Tags: + - Key: Name + Value: !Sub '${ServerName}-EFS-SecurityGroup' + + # Time Sync Service egress rule for Minecraft Server + MinecraftToTimeSyncEgress: + Type: 'AWS::EC2::SecurityGroupEgress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: udp + FromPort: 123 + ToPort: 123 + CidrIp: '169.254.169.123/32' + Description: 'Allow NTP outbound to AWS Time Sync Service' + + # Time Sync Service ingress rule for Minecraft Server + MinecraftFromTimeSyncIngress: + Type: 'AWS::EC2::SecurityGroupIngress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: udp + FromPort: 123 + ToPort: 123 + CidrIp: '169.254.169.123/32' + Description: 'Allow NTP inbound from AWS Time Sync Service' + + # EFS to Minecraft Server rules + EFSFromMinecraftNFSIngress: + Type: 'AWS::EC2::SecurityGroupIngress' + Properties: + GroupId: !Ref EFSSecurityGroup + IpProtocol: tcp + FromPort: 2049 + ToPort: 2049 + SourceSecurityGroupId: !Ref MinecraftServerSecurityGroup + Description: 'Allow NFS inbound from Minecraft server' + + # Mount helper ports (TCP and UDP) + EFSFromMinecraftMountHelperTCPIngress: + Type: 'AWS::EC2::SecurityGroupIngress' + Properties: + GroupId: !Ref EFSSecurityGroup + IpProtocol: tcp + FromPort: 988 + ToPort: 988 + SourceSecurityGroupId: !Ref MinecraftServerSecurityGroup + Description: 'Allow mount helper TCP inbound from Minecraft server' + + EFSFromMinecraftMountHelperUDPIngress: + Type: 'AWS::EC2::SecurityGroupIngress' + Properties: + GroupId: !Ref EFSSecurityGroup + IpProtocol: udp + FromPort: 988 + ToPort: 988 + SourceSecurityGroupId: !Ref MinecraftServerSecurityGroup + Description: 'Allow mount helper UDP inbound from Minecraft server' + + # Minecraft Server to EFS rules + MinecraftToEFSNFSEgress: + Type: 'AWS::EC2::SecurityGroupEgress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: tcp + FromPort: 2049 + ToPort: 2049 + DestinationSecurityGroupId: !Ref EFSSecurityGroup + Description: 'Allow NFS outbound to EFS' + + MinecraftToEFSMountHelperTCPEgress: + Type: 'AWS::EC2::SecurityGroupEgress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: tcp + FromPort: 988 + ToPort: 988 + DestinationSecurityGroupId: !Ref EFSSecurityGroup + Description: 'Allow mount helper TCP outbound to EFS' + + MinecraftToEFSMountHelperUDPEgress: + Type: 'AWS::EC2::SecurityGroupEgress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: udp + FromPort: 988 + ToPort: 988 + DestinationSecurityGroupId: !Ref EFSSecurityGroup + Description: 'Allow mount helper UDP outbound to EFS' + + # Separate Security Group Rules + NetworkLoadBalancerToMinecraftEgress: + Type: 'AWS::EC2::SecurityGroupEgress' + Properties: + GroupId: !Ref NetworkLoadBalancerSecurityGroup + IpProtocol: tcp + FromPort: !Ref MinecraftPort + ToPort: !Ref MinecraftPort + DestinationSecurityGroupId: !Ref MinecraftServerSecurityGroup + Description: !Sub 'Allow outbound traffic to Minecraft server on port ${MinecraftPort}' + + MinecraftFromLoadBalancerIngress: + Type: 'AWS::EC2::SecurityGroupIngress' + Properties: + GroupId: !Ref MinecraftServerSecurityGroup + IpProtocol: tcp + FromPort: !Ref MinecraftPort + ToPort: !Ref MinecraftPort + SourceSecurityGroupId: !Ref NetworkLoadBalancerSecurityGroup + Description: !Sub 'Allow Minecraft traffic from Load Balancer on port ${MinecraftPort}' + + # S3 Bucket for NLB Access Logs + LoadBalancerLogsBucket: + Type: 'AWS::S3::Bucket' + Metadata: + checkov: + skip: + - id: CKV_AWS_18 + comment: "Access logs bucket does not require its own access logging to avoid recursive logging" + Properties: + BucketName: !Sub '${ServerName}-nlb-logs-${AWS::AccountId}-${AWS::Region}' + VersioningConfiguration: + Status: Enabled + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + LifecycleConfiguration: + Rules: + - Id: DeleteOldLogs + Status: Enabled + ExpirationInDays: 90 + Tags: + - Key: Name + Value: !Sub '${ServerName}-nlb-logs' + + # Bucket Policy for NLB Access Logs + LoadBalancerLogsBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: !Ref LoadBalancerLogsBucket + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: AllowNLBAccessLogs + Effect: Allow + Principal: + AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root' + Action: 's3:PutObject' + Resource: !Sub '${LoadBalancerLogsBucket.Arn}/*' + - Sid: AllowSSLRequestsOnly + Effect: Deny + Principal: '*' + Action: 's3:*' + Resource: + - !GetAtt LoadBalancerLogsBucket.Arn + - !Sub '${LoadBalancerLogsBucket.Arn}/*' + Condition: + Bool: + 'aws:SecureTransport': false + + NetworkLoadBalancer: + Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer' + Properties: + Type: network + Scheme: internet-facing + Subnets: !Split + - ',' + - Fn::ImportValue: !Sub '${ServerName}-public-subnets' + LoadBalancerAttributes: + - Key: load_balancing.cross_zone.enabled + Value: true + - Key: deletion_protection.enabled + Value: false + - Key: access_logs.s3.enabled + Value: true + - Key: access_logs.s3.bucket + Value: !Ref LoadBalancerLogsBucket + - Key: access_logs.s3.prefix + Value: !Sub '${ServerName}-nlb-logs' + Tags: + - Key: 'Name' + Value: !Sub '${ServerName}-NLB' + + MinecraftTargetGroup: + Type: 'AWS::ElasticLoadBalancingV2::TargetGroup' + Properties: + Port: !Ref MinecraftPort + Protocol: TCP + VpcId: + Fn::ImportValue: !Sub '${ServerName}-vpc-id' + TargetType: ip + HealthCheckEnabled: true + HealthCheckPort: !Ref MinecraftPort + HealthCheckProtocol: TCP + HealthCheckIntervalSeconds: 30 + HealthCheckTimeoutSeconds: 10 + HealthyThresholdCount: 3 + UnhealthyThresholdCount: 3 + TargetGroupAttributes: + - Key: deregistration_delay.timeout_seconds + Value: '30' + Tags: + - Key: Name + Value: !Sub '${ServerName}-TargetGroup' + + MinecraftListener: + Type: 'AWS::ElasticLoadBalancingV2::Listener' + Properties: + LoadBalancerArn: !Ref NetworkLoadBalancer + Port: !Ref MinecraftPort + Protocol: TCP + DefaultActions: + - Type: forward + TargetGroupArn: !Ref MinecraftTargetGroup + + # EFS Configuration + FileSystem: + Type: 'AWS::EFS::FileSystem' + DeletionPolicy: 'Delete' + UpdateReplacePolicy: 'Retain' + Properties: + PerformanceMode: generalPurpose + ThroughputMode: bursting + Encrypted: true + KmsKeyId: !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/elasticfilesystem' + FileSystemTags: + - Key: Name + Value: !Sub '${ServerName}-EFS' + + # EFS Mount Targets + MountTarget1: + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref FileSystem + SubnetId: !Select + - 0 + - !Split + - ',' + - Fn::ImportValue: !Sub '${ServerName}-private-subnets' + SecurityGroups: + - !Ref EFSSecurityGroup + + MountTarget2: + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: !Ref FileSystem + SubnetId: !Select + - 1 + - !Split + - ',' + - Fn::ImportValue: !Sub '${ServerName}-private-subnets' + SecurityGroups: + - !Ref EFSSecurityGroup + + # IAM Configuration + InstanceRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: ec2.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' + Policies: + - PolicyName: 'MinecraftServerPolicy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - 's3:GetObject' + - 's3:ListBucket' + Resource: + - !Sub 'arn:${AWS::Partition}:s3:::${MinecraftBucket}' + - !Sub 'arn:${AWS::Partition}:s3:::${MinecraftBucket}/*' + - Effect: Allow + Action: + - 'ec2:DescribeInstances' + - 'ec2:StopInstances' + - 'ec2:CreateTags' + Resource: !Sub 'arn:${AWS::Partition}:ec2:${AWS::Region}:${AWS::AccountId}:instance/*' + Condition: + StringEquals: + 'aws:RequestTag/Name': !Sub '${ServerName}-*' + - Effect: Allow + Action: + - 'cloudwatch:PutMetricData' + Resource: '*' + Condition: + StringEquals: + 'cloudwatch:namespace': 'Minecraft' + + InstanceProfile: + Type: 'AWS::IAM::InstanceProfile' + Properties: + Path: '/' + Roles: + - !Ref InstanceRole + + # Launch Template + LaunchTemplate: + Type: 'AWS::EC2::LaunchTemplate' + Properties: + LaunchTemplateData: + ImageId: !Ref AMI + InstanceType: !Ref InstanceType + IamInstanceProfile: + Name: !Ref InstanceProfile + SecurityGroupIds: + - !Ref MinecraftServerSecurityGroup + MetadataOptions: + HttpEndpoint: enabled + HttpTokens: required + HttpPutResponseHopLimit: 1 + InstanceMarketOptions: + MarketType: spot + SpotOptions: + SpotInstanceType: one-time + InstanceInterruptionBehavior: terminate + UserData: + Fn::Base64: !Sub | + #!/bin/bash + # Install required packages + dnf install -y aws-cli + + # Download and run initialization script + aws s3 cp s3://${MinecraftBucket}/scripts/initialize_instance.sh /tmp/ + chmod +x /tmp/initialize_instance.sh + /tmp/initialize_instance.sh "${FileSystem}" "${MinecraftBucket}" "${InactivityShutdownMinutes}" "${MinecraftPort}" + diff --git a/aws/cloudformation/pre-github-connection.yml b/aws/cloudformation/pre-github-connection.yml new file mode 100644 index 0000000..4d539a4 --- /dev/null +++ b/aws/cloudformation/pre-github-connection.yml @@ -0,0 +1,145 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Creates GitHub OIDC provider and IAM role for GitHub Actions' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: 'GitHub Configuration' + Parameters: + - GitHubOrg + - RepositoryName + - StackNamePrefix + ParameterLabels: + GitHubOrg: + default: 'GitHub Organization' + RepositoryName: + default: 'Repository Name' + StackNamePrefix: + default: 'Stack Name Prefix' + + AWS::CloudFormation::Stack: + Tags: + - Key: 'awsApplication' + Value: 'Minecraft' + +Parameters: + GitHubOrg: + Type: String + Description: 'GitHub organization or username' + + RepositoryName: + Type: String + Description: 'GitHub repository name' + + StackNamePrefix: + Type: String + Description: 'Prefix for CloudFormation stacks this role can manage' + Default: 'minecraft' + +Resources: + GitHubOIDCProvider: + Type: AWS::IAM::OIDCProvider + Properties: + Url: 'https://token.actions.githubusercontent.com' + ClientIdList: + - 'sts.amazonaws.com' + ThumbprintList: + - '6938fd4d98bab03faadb97b34396831e3780aea1' + Tags: + - Key: 'Purpose' + Value: 'GitHubActions' + - Key: 'awsApplication' + Value: 'Minecraft' + + GitHubActionsRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${AWS::StackName}-github-actions-role' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Principal: + Federated: !GetAtt GitHubOIDCProvider.Arn + Action: 'sts:AssumeRoleWithWebIdentity' + Condition: + StringEquals: + 'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com' + StringLike: + 'token.actions.githubusercontent.com:sub': !Sub 'repo:${GitHubOrg}/${RepositoryName}:*' + ManagedPolicyArns: + - !Sub 'arn:${AWS::Partition}:iam::aws:policy/PowerUserAccess' + Policies: + - PolicyName: 'PassRolePolicy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Action: 'iam:PassRole' + Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${StackNamePrefix}-*' + Condition: + StringEquals: + 'iam:PassedToService': 'cloudformation.amazonaws.com' + PermissionsBoundary: !Ref PermissionsBoundary + Tags: + - Key: 'awsApplication' + Value: 'Minecraft' + + PermissionsBoundary: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + ManagedPolicyName: !Sub '${StackNamePrefix}-boundary' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Action: + - 'cloudformation:*' + Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${StackNamePrefix}-*/*' + + - Effect: 'Allow' + Action: + - 'cloudformation:ListStacks' + - 'cloudformation:DescribeStacks' + - 'cloudformation:ListStackResources' + Resource: '*' + + - Effect: 'Allow' + NotAction: + - 'organizations:*' + - 'account:*' + Resource: '*' + Condition: + StringEquals: + 'aws:CalledVia': ['cloudformation.amazonaws.com'] + StringLike: + 'aws:CalledViaFirst': 'cloudformation.amazonaws.com' + 'aws:ResourceTag/aws:cloudformation:stack-name': !Sub '${StackNamePrefix}-*' + + - Effect: 'Allow' + NotAction: + - 'cloudformation:*' + Resource: '*' + Condition: + StringEquals: + 'aws:RequestTag/Application': 'Minecraft' + + - Effect: 'Deny' + NotAction: + - 'cloudformation:*' + Resource: '*' + Condition: + StringNotEquals: + 'aws:CalledVia': ['cloudformation.amazonaws.com'] + + +Outputs: + OIDCProviderArn: + Description: 'ARN of the GitHub OIDC provider' + Value: !GetAtt GitHubOIDCProvider.Arn + + RoleArn: + Description: 'ARN of the IAM role for GitHub Actions' + Value: !GetAtt GitHubActionsRole.Arn diff --git a/aws/cloudformation/s3-bucket.yml b/aws/cloudformation/s3-bucket.yml new file mode 100644 index 0000000..c8d4ccd --- /dev/null +++ b/aws/cloudformation/s3-bucket.yml @@ -0,0 +1,137 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Creates a secure S3 bucket for Minecraft server configuration and artifacts' + +Resources: + MinecraftBucket: + Type: 'AWS::S3::Bucket' + DeletionPolicy: 'Delete' + UpdateReplacePolicy: 'Retain' + Properties: + BucketName: !Sub '${AWS::StackName}-minecraft' + VersioningConfiguration: + Status: 'Enabled' + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: 'AES256' + BucketKeyEnabled: true + LoggingConfiguration: + DestinationBucketName: !Ref 'AccessLogsBucket' + LogFilePrefix: 'minecraft-bucket-logs/' + LifecycleConfiguration: + Rules: + - Id: 'DeleteOldVersions' + Status: 'Enabled' + NoncurrentVersionExpiration: + NoncurrentDays: 90 + - Id: 'AbortIncompleteUploads' + Status: 'Enabled' + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 7 + + AccessLogsBucket: + Type: 'AWS::S3::Bucket' + DeletionPolicy: 'Delete' + UpdateReplacePolicy: 'Retain' + Metadata: + checkov: + skip: + - id: CKV_AWS_18 + comment: "Access logs bucket does not require its own access logging to avoid recursive logging" + Properties: + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: 'AES256' + BucketKeyEnabled: true + VersioningConfiguration: + Status: 'Enabled' + LifecycleConfiguration: + Rules: + - Id: 'DeleteOldLogs' + Status: 'Enabled' + ExpirationInDays: 365 + - Id: 'AbortIncompleteUploads' + Status: 'Enabled' + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 7 + + MinecraftBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: !Ref 'MinecraftBucket' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: 'EnforceSSLOnly' + Effect: 'Deny' + Principal: '*' + Action: 's3:*' + Resource: + - !Sub '${MinecraftBucket.Arn}/*' + - !GetAtt 'MinecraftBucket.Arn' + Condition: + Bool: + 'aws:SecureTransport': false + - Sid: 'EnforceSecureCommunication' + Effect: 'Deny' + Principal: '*' + Action: 's3:*' + Resource: + - !Sub '${MinecraftBucket.Arn}/*' + - !GetAtt 'MinecraftBucket.Arn' + Condition: + NumericLessThan: + 's3:TlsVersion': 1.2 + + AccessLogsBucketPolicy: + Type: 'AWS::S3::BucketPolicy' + Properties: + Bucket: !Ref 'AccessLogsBucket' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Sid: 'EnforceSSLOnly' + Effect: 'Deny' + Principal: '*' + Action: 's3:*' + Resource: + - !Sub '${AccessLogsBucket.Arn}/*' + - !GetAtt 'AccessLogsBucket.Arn' + Condition: + Bool: + 'aws:SecureTransport': false + - Sid: 'EnforceSecureCommunication' + Effect: 'Deny' + Principal: '*' + Action: 's3:*' + Resource: + - !Sub '${AccessLogsBucket.Arn}/*' + - !GetAtt 'AccessLogsBucket.Arn' + Condition: + NumericLessThan: + 's3:TlsVersion': 1.2 + +Outputs: + BucketName: + Description: 'Name of the created S3 bucket' + Value: !Ref 'MinecraftBucket' + Export: + Name: !Sub '${AWS::StackName}-bucket-name' + + BucketArn: + Description: 'ARN of the created S3 bucket' + Value: !GetAtt 'MinecraftBucket.Arn' + Export: + Name: !Sub '${AWS::StackName}-bucket-arn' diff --git a/aws/cloudformation/vpc.yml b/aws/cloudformation/vpc.yml new file mode 100644 index 0000000..eb645bc --- /dev/null +++ b/aws/cloudformation/vpc.yml @@ -0,0 +1,573 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'VPC Infrastructure' + +Parameters: + VpcCidrBlock: + Type: 'String' + Description: 'The CIDR block for the VPC' + + ServerName: + Type: 'String' + Description: 'Name of the Minecraft server' + Default: 'minecraft-server' + + MinecraftPort: + Type: 'Number' + Description: 'Minecraft server port' + Default: 25565 + MinValue: 1024 + MaxValue: 65535 + +Resources: + VPC: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: !Ref VpcCidrBlock + EnableDnsHostnames: true + EnableDnsSupport: true + Tags: + - Key: 'Name' + Value: !Sub 'vpc-${AWS::Region}' + + PublicSubnet1: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref VPC + CidrBlock: !Select [0, !Cidr [!Ref VpcCidrBlock, 2, 26]] + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: 'Name' + Value: !Sub 'public-subnet-1-${AWS::Region}' + + PublicSubnet2: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref VPC + CidrBlock: !Select [1, !Cidr [!Ref VpcCidrBlock, 2, 26]] + AvailabilityZone: !Select [1, !GetAZs ''] + Tags: + - Key: 'Name' + Value: !Sub 'public-subnet-2-${AWS::Region}' + + PrivateSubnet1: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref VPC + CidrBlock: !Select [2, !Cidr [!Ref VpcCidrBlock, 2, 26]] + AvailabilityZone: !Select [0, !GetAZs ''] + Tags: + - Key: 'Name' + Value: !Sub 'private-subnet-1-${AWS::Region}' + + PrivateSubnet2: + Type: 'AWS::EC2::Subnet' + Properties: + VpcId: !Ref VPC + CidrBlock: !Select [3, !Cidr [!Ref VpcCidrBlock, 2, 26]] + AvailabilityZone: !Select [1, !GetAZs ''] + Tags: + - Key: 'Name' + Value: !Sub 'private-subnet-2-${AWS::Region}' + + # Internet Gateway + InternetGateway: + Type: 'AWS::EC2::InternetGateway' + Properties: + Tags: + - Key: Name + Value: !Sub '${ServerName}-igw' + + AttachGateway: + Type: 'AWS::EC2::VPCGatewayAttachment' + Properties: + VpcId: !Ref VPC + InternetGatewayId: !Ref InternetGateway + + # NAT Gateway + NatGatewayEIP: + Type: 'AWS::EC2::EIP' + Properties: + Domain: vpc + + NatGateway: + Type: 'AWS::EC2::NatGateway' + Properties: + AllocationId: !GetAtt NatGatewayEIP.AllocationId + SubnetId: !Ref PublicSubnet1 + Tags: + - Key: Name + Value: !Sub '${ServerName}-nat' + + + # Route Tables + PublicRouteTable: + Type: 'AWS::EC2::RouteTable' + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${ServerName}-public-rt' + + PrivateRouteTable: + Type: 'AWS::EC2::RouteTable' + Properties: + VpcId: !Ref VPC + Tags: + - Key: Name + Value: !Sub '${ServerName}-private-rt' + + PublicRoute: + Type: 'AWS::EC2::Route' + DependsOn: AttachGateway + Properties: + RouteTableId: !Ref PublicRouteTable + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: !Ref InternetGateway + + PrivateRoute: + Type: 'AWS::EC2::Route' + Properties: + RouteTableId: !Ref PrivateRouteTable + DestinationCidrBlock: '0.0.0.0/0' + NatGatewayId: !Ref NatGateway + + PublicSubnet1RouteTableAssociation: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref PublicSubnet1 + RouteTableId: !Ref PublicRouteTable + + PublicSubnet2RouteTableAssociation: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref PublicSubnet2 + RouteTableId: !Ref PublicRouteTable + + PrivateSubnet1RouteTableAssociation: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref PrivateSubnet1 + RouteTableId: !Ref PrivateRouteTable + + PrivateSubnet2RouteTableAssociation: + Type: 'AWS::EC2::SubnetRouteTableAssociation' + Properties: + SubnetId: !Ref PrivateSubnet2 + RouteTableId: !Ref PrivateRouteTable + + PublicNACL: + Type: 'AWS::EC2::NetworkAcl' + Properties: + VpcId: !Ref VPC + Tags: + - Key: 'Name' + Value: !Sub 'public-nacl-${AWS::Region}' + + PrivateNACL: + Type: 'AWS::EC2::NetworkAcl' + Properties: + VpcId: !Ref VPC + Tags: + - Key: 'Name' + Value: !Sub 'private-nacl-${AWS::Region}' + + # Public NACL Rules + PublicNACLInboundHTTPS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PublicNACL + RuleNumber: 100 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: '0.0.0.0/0' + PortRange: + From: 443 + To: 443 + + PublicNACLOutboundHTTPS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PublicNACL + RuleNumber: 100 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: '0.0.0.0/0' + PortRange: + From: 443 + To: 443 + + PublicNACLInboundMinecraft: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PublicNACL + RuleNumber: 200 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: '0.0.0.0/0' + PortRange: + From: !Ref 'MinecraftPort' + To: !Ref 'MinecraftPort' + + PublicNACLOutboundMinecraft: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PublicNACL + RuleNumber: 200 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: '0.0.0.0/0' + PortRange: + From: !Ref 'MinecraftPort' + To: !Ref 'MinecraftPort' + + # Private NACL Rules + PrivateNACLInboundHTTPS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 100 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: '0.0.0.0/0' + PortRange: + From: 443 + To: 443 + + PrivateNACLOutboundHTTPS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 100 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: '0.0.0.0/0' + PortRange: + From: 443 + To: 443 + + # EFS NACL Rules for Private Subnet + PrivateNACLInboundEFS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 300 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 2049 + To: 2049 + + PrivateNACLOutboundEFS: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 300 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 2049 + To: 2049 + + # Add Mount Helper TCP Rules + PrivateNACLInboundEFSMountHelper: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 301 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 988 + To: 988 + + PrivateNACLOutboundEFSMountHelper: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 301 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 988 + To: 988 + + # Add Mount Helper UDP Rules + PrivateNACLInboundEFSMountHelperUDP: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 302 + Protocol: 17 # UDP + RuleAction: 'allow' + Egress: false + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 988 + To: 988 + + PrivateNACLOutboundEFSMountHelperUDP: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 302 + Protocol: 17 # UDP + RuleAction: 'allow' + Egress: true + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 988 + To: 988 + + # AWS Time Sync Service NACL Rules - Only port 123 needed + PrivateNACLInboundTimeSync: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 303 + Protocol: 17 # UDP + RuleAction: 'allow' + Egress: false + CidrBlock: '169.254.169.123/32' + PortRange: + From: 123 + To: 123 + + PrivateNACLOutboundTimeSync: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 303 + Protocol: 17 # UDP + RuleAction: 'allow' + Egress: true + CidrBlock: '169.254.169.123/32' + PortRange: + From: 123 + To: 123 + + # EFS Response Ports + PrivateNACLInboundEFSResponse: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 310 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: false + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 1024 + To: 65535 + + PrivateNACLOutboundEFSResponse: + Type: 'AWS::EC2::NetworkAclEntry' + Properties: + NetworkAclId: !Ref PrivateNACL + RuleNumber: 310 + Protocol: 6 # TCP + RuleAction: 'allow' + Egress: true + CidrBlock: !Ref VpcCidrBlock + PortRange: + From: 1024 + To: 65535 + + # Don't forget to associate the NACLs with their respective subnets + PublicSubnet1NACLAssociation: + Type: 'AWS::EC2::SubnetNetworkAclAssociation' + Properties: + SubnetId: !Ref PublicSubnet1 + NetworkAclId: !Ref PublicNACL + + PublicSubnet2NACLAssociation: + Type: 'AWS::EC2::SubnetNetworkAclAssociation' + Properties: + SubnetId: !Ref PublicSubnet2 + NetworkAclId: !Ref PublicNACL + + PrivateSubnet1NACLAssociation: + Type: 'AWS::EC2::SubnetNetworkAclAssociation' + Properties: + SubnetId: !Ref PrivateSubnet1 + NetworkAclId: !Ref PrivateNACL + + PrivateSubnet2NACLAssociation: + Type: 'AWS::EC2::SubnetNetworkAclAssociation' + Properties: + SubnetId: !Ref PrivateSubnet2 + NetworkAclId: !Ref PrivateNACL + + FlowLogsRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Principal: + Service: 'vpc-flow-logs.amazonaws.com' + Action: 'sts:AssumeRole' + Policies: + - PolicyName: 'FlowLogsPolicy' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Action: + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:PutLogEvents' + - 'logs:DescribeLogGroups' + - 'logs:DescribeLogStreams' + Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/vpc/flowlogs/*' + + FlowLogsKey: + Type: 'AWS::KMS::Key' + DeletionPolicy: 'Delete' + UpdateReplacePolicy: 'Delete' + Properties: + Description: 'KMS key for encrypting flow logs' + KeyPolicy: + Version: '2012-10-17' + Statement: + - Effect: 'Allow' + Principal: + AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root' + Action: + - 'kms:*' + Resource: '*' + - Effect: 'Allow' + Principal: + Service: 'logs.amazonaws.com' + Action: + - 'kms:Encrypt*' + - 'kms:Decrypt*' + - 'kms:ReEncrypt*' + - 'kms:GenerateDataKey*' + - 'kms:Describe*' + Resource: '*' + Condition: + ArnLike: + 'kms:EncryptionContext:aws:logs:arn': !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:*' + KeyUsage: 'ENCRYPT_DECRYPT' + EnableKeyRotation: true + + FlowLogsGroup: + Type: 'AWS::Logs::LogGroup' + DeletionPolicy: 'Delete' + UpdateReplacePolicy: 'Retain' + Properties: + LogGroupName: !Sub '/aws/vpc/flowlogs/${AWS::StackName}' + RetentionInDays: '30' + KmsKeyId: !GetAtt 'FlowLogsKey.Arn' + + VPCFlowLog: + Type: 'AWS::EC2::FlowLog' + Properties: + DeliverLogsPermissionArn: !GetAtt FlowLogsRole.Arn + LogGroupName: !Ref FlowLogsGroup + ResourceId: !Ref VPC + ResourceType: 'VPC' + TrafficType: 'ALL' + + SSMSecurityGroup: + Type: 'AWS::EC2::SecurityGroup' + Properties: + GroupDescription: 'Security group for SSM endpoints' + VpcId: !Ref VPC + + # VPC Endpoints for SSM + SSMEndpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcId: !Ref VPC + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm' + VpcEndpointType: Interface + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 + SecurityGroupIds: + - !Ref SSMSecurityGroup + PrivateDnsEnabled: true + + SSMMessagesEndpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcId: !Ref VPC + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages' + VpcEndpointType: Interface + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 + SecurityGroupIds: + - !Ref SSMSecurityGroup + PrivateDnsEnabled: true + + EC2MessagesEndpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcId: !Ref VPC + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages' + VpcEndpointType: Interface + SubnetIds: + - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 + SecurityGroupIds: + - !Ref SSMSecurityGroup + PrivateDnsEnabled: true + + # S3 Gateway Endpoint + S3Endpoint: + Type: 'AWS::EC2::VPCEndpoint' + Properties: + VpcId: !Ref VPC + ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3' + VpcEndpointType: Gateway + RouteTableIds: + - !Ref PrivateRouteTable + +Outputs: + VpcIdExport: + Description: 'VPC ID' + Value: !Ref VPC + Export: + Name: !Sub '${ServerName}-vpc-id' + + PublicSubnetsExport: + Description: 'Public Subnet IDs' + Value: !Join + - ',' + - - !Ref PublicSubnet1 + - !Ref PublicSubnet2 + Export: + Name: !Sub '${ServerName}-public-subnets' + + PrivateSubnetsExport: + Description: 'Private Subnet IDs' + Value: !Join + - ',' + - - !Ref PrivateSubnet1 + - !Ref PrivateSubnet2 + Export: + Name: !Sub '${ServerName}-private-subnets' + + SSMSecurityGroupExport: + Description: 'SSM Security Group' + Value: !Ref SSMSecurityGroup + Export: + Name: !Sub '${ServerName}-ssm-security-group' diff --git a/aws/docker-compose.yml b/aws/docker-compose.yml new file mode 100644 index 0000000..dcb0a1c --- /dev/null +++ b/aws/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3.8' +services: + minecraft: + image: 'itzg/minecraft-server' + environment: + EULA: 'TRUE' + TYPE: 'PAPER' + MEMORY: '2G' + ENABLE_RCON: 'true' + RCON_PASSWORD: '${RCON_PASSWORD}' + RCON_PORT: '25575' + ports: + - '25565:25565' + - '25575:25575' + volumes: + - '/efs/minecraft:/data' + restart: 'unless-stopped' + healthcheck: + test: 'mc-health' + interval: '30s' + timeout: '10s' + retries: 3 + start_period: '60s' + + rcon: + image: 'itzg/rcon' + environment: + RWA_PASSWORD: '${RCON_PASSWORD}' + RWA_HOST: 'minecraft' + RWA_PORT: '25575' + depends_on: + - 'minecraft' + + monitor: + image: 'python:3.12-alpine' + volumes: + - '/efs/minecraft:/data' + - './scripts:/scripts' + environment: + RCON_HOST: 'minecraft' + RCON_PORT: '25575' + RCON_PASSWORD: '${RCON_PASSWORD}' + INACTIVITY_SHUTDOWN_MINUTES: '${INACTIVITY_SHUTDOWN_MINUTES:-30}' + command: 'sh -c "apk add --no-cache aws-cli && pip install mcrcon boto3 requests && while true; do python /scripts/check_activity.py; sleep 60; done"' + depends_on: + - 'minecraft' + - 'rcon' \ No newline at end of file diff --git a/aws/lambda/stopped_instance_check.py b/aws/lambda/stopped_instance_check.py new file mode 100644 index 0000000..95ac0ac --- /dev/null +++ b/aws/lambda/stopped_instance_check.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +""" +AWS Lambda function to check and terminate stopped EC2 instances based on stop duration. + +This module checks for EC2 instances that have been stopped for longer than a configured +duration and terminates them. It uses instance tags to track stop times and supports +configurable termination thresholds. + +Environment Variables: + TERMINATE_AFTER_DAYS (int): Number of days after which a stopped instance should be terminated +""" + +import logging +import os +from datetime import datetime, timezone + +import boto3 +from botocore.exceptions import BotoCoreError, ClientError + +# Configure logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) + + +class InstanceTerminationError(Exception): + """Custom exception for instance termination failures.""" + + +def get_stop_time_from_tags(tags): + """ + Extract the StopTime from instance tags. + + Args: + tags (list): List of EC2 instance tags + + Returns: + datetime: The parsed stop time if found and valid, None otherwise + """ + if not tags: + return None + + for tag in tags: + if tag["Key"] == "StopTime": + try: + return datetime.fromisoformat(tag["Value"]) + except ValueError as err: + logger.error("Invalid date format in StopTime tag: %s", err) + return None + return None + + +def should_terminate_instance(stop_time, terminate_after_days): + """ + Determine if an instance should be terminated based on its stop time. + + Args: + stop_time (datetime): When the instance was stopped + terminate_after_days (int): Number of days after which to terminate + + Returns: + bool: True if instance should be terminated, False otherwise + """ + if not stop_time: + return False + + days_stopped = (datetime.now(timezone.utc) - stop_time).days + return days_stopped >= terminate_after_days + + +def terminate_instance(ec2_client, instance_id): + """ + Terminate an EC2 instance. + + Args: + ec2_client: boto3 EC2 client + instance_id (str): ID of the instance to terminate + + Raises: + InstanceTerminationError: If termination fails + """ + try: + ec2_client.terminate_instances(InstanceIds=[instance_id]) + logger.info("Successfully initiated termination of instance %s", instance_id) + except (BotoCoreError, ClientError) as err: + raise InstanceTerminationError( + f"Failed to terminate instance {instance_id}" + ) from err + + +def get_stopped_instances(ec2_client): + """ + Get all stopped EC2 instances with StopTime tags. + + Args: + ec2_client: boto3 EC2 client + + Returns: + list: List of stopped EC2 instances + + Raises: + ClientError: If AWS API call fails + """ + try: + response = ec2_client.describe_instances( + Filters=[ + {"Name": "instance-state-name", "Values": ["stopped"]}, + {"Name": "tag-key", "Values": ["StopTime"]}, + ] + ) + instances = [] + for reservation in response["Reservations"]: + instances.extend(reservation["Instances"]) + return instances + except ClientError as err: + logger.error("Failed to get stopped instances: %s", err) + raise + + +def handler(event, context): + """ + Lambda function handler to check and terminate stopped instances. + + Args: + event: AWS Lambda event object + context: AWS Lambda context object + + Returns: + dict: Summary of actions taken + """ + try: + terminate_days = int(os.environ["TERMINATE_AFTER_DAYS"]) + except (KeyError, ValueError) as err: + logger.error("Invalid TERMINATE_AFTER_DAYS configuration: %s", err) + raise ValueError("TERMINATE_AFTER_DAYS must be a valid integer") from err + + ec2_client = boto3.client("ec2") + termination_summary = { + "terminated_instances": [], + "failed_terminations": [], + "checked_instances": 0, + } + + try: + instances = get_stopped_instances(ec2_client) + termination_summary["checked_instances"] = len(instances) + + for instance in instances: + instance_id = instance["InstanceId"] + stop_time = get_stop_time_from_tags(instance.get("Tags", [])) + + if not stop_time: + logger.warning( + "Instance %s has no valid StopTime tag, skipping", instance_id + ) + continue + + days_stopped = (datetime.now(timezone.utc) - stop_time).days + logger.info( + "Instance %s has been stopped for %d days", instance_id, days_stopped + ) + + if should_terminate_instance(stop_time, terminate_days): + try: + terminate_instance(ec2_client, instance_id) + termination_summary["terminated_instances"].append(instance_id) + except InstanceTerminationError as err: + logger.error(err) + termination_summary["failed_terminations"].append(instance_id) + + except ClientError as err: + logger.error("AWS API error: %s", err) + raise + + logger.info("Termination summary: %s", termination_summary) + return termination_summary + + +if __name__ == "__main__": + # For local testing + test_event = {} + test_context = None + print(handler(test_event, test_context)) diff --git a/aws/scripts/initialize_instance.sh b/aws/scripts/initialize_instance.sh new file mode 100644 index 0000000..b9eb846 --- /dev/null +++ b/aws/scripts/initialize_instance.sh @@ -0,0 +1,297 @@ +#!/bin/bash +# +# Minecraft Server Instance Initialization Script +# This script initializes an EC2 instance for running a Minecraft server. +# It handles EFS mounting, Docker setup, and environment configuration. +# +# Arguments: +# $1 - EFS File System ID +# $2 - Stack Name +# $3 - Inactivity timeout in minutes +# $4 - Minecraft server port +# +# Exit codes: +# 0 - Success +# 1 - Missing required arguments +# 2 - EFS mount failure +# 3 - Docker setup failure +# 4 - Environment setup failure + +# Enable strict error handling +set -uo pipefail +IFS=$'\n\t' + +####################################### +# Print error message to stderr +# Arguments: +# $1 - Error message +####################################### +error() { + echo "ERROR: ${1}" >&2 +} + +####################################### +# Print info message to stdout +# Arguments: +# $1 - Info message +####################################### +info() { + echo "INFO: ${1}" >&2 +} + +####################################### +# Validate input parameters +# Arguments: +# $1 - EFS File System ID +# $2 - MinecraftBucket +# $3 - Inactivity timeout (optional) +# Returns: +# 0 if valid, 1 if invalid +####################################### +validate_inputs() { + if [[ $# -lt 4 ]]; then + error "Missing required arguments" + echo "Usage: ${0} " >&2 + return 1 + fi +} + +####################################### +# Mount EFS filesystem +# Arguments: +# $1 - Mount point +# $2 - EFS File System ID +# Returns: +# 0 if successful, 2 if failed +####################################### +mount_efs() { + local mount_point="${1}" + local fs_id="${2}" + + info "Mounting EFS filesystem ${fs_id} at ${mount_point}" + + mkdir -p "${mount_point}" + + if mountpoint -q "${mount_point}"; then + info "EFS already mounted at ${mount_point}" + return 0 + fi + + if ! mount -t efs "${fs_id}:/" "${mount_point}"; then + error "Failed to mount EFS at ${mount_point}" + return 2 + fi + + info "Successfully mounted EFS at ${mount_point}" + + if ! grep -q "${fs_id}" /etc/fstab; then + echo "${fs_id}:/ ${mount_point} efs defaults,_netdev 0 0" >>/etc/fstab + fi +} + +####################################### +# Setup Docker and dependencies +# Returns: +# 0 if successful, 3 if failed +####################################### +setup_docker() { + info "Setting up Docker and dependencies" + + # Install required packages + if ! dnf install -y amazon-efs-utils docker; then + error "Failed to install required packages" + return 3 + fi + + # Install Docker Compose if not present + if [[ ! -f /usr/local/bin/docker-compose ]]; then + info "Installing Docker Compose" + if ! curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-aarch64" -o /usr/local/bin/docker-compose; then + error "Failed to download Docker Compose" + return 3 + fi + chmod +x /usr/local/bin/docker-compose + fi + + # Enable and start Docker + if ! systemctl enable docker && systemctl start docker; then + error "Failed to enable and start Docker" + return 3 + fi + + info "Docker setup completed successfully" +} + +####################################### +# Setup environment configuration +# Arguments: +# $1 - EFS mount point +# $2 - MinecraftBucket +# $3 - Inactivity timeout in minutes +# $4 - Minecraft server port +# Returns: +# 0 if successful, 4 if failed +####################################### +setup_environment() { + local efs_mount="${1}" + local minecraft_bucket="${2}" + local inactivity_minutes="${3}" + local minecraft_port="${4}" + + info "Setting up environment configuration" + + if [[ ! -f "${efs_mount}/.env" ]]; then + info "Generating new configuration" + local RCON_PASSWORD + RCON_PASSWORD=$(openssl rand -base64 12) + if ! cat >"${efs_mount}/.env" </dev/null); then + error "Failed to get ETag for docker-compose.yml" + return 4 + fi + + # Remove quotes from ETag + s3_etag=$(echo "${s3_etag}" | tr -d '"') + local current_etag="" + + # Read current ETag if it exists + if [[ -f ${compose_etag_file} ]]; then + current_etag=$(cat "${compose_etag_file}") + fi + + # Download if file doesn't exist or ETag is different + if [[ ! -f "${efs_mount}/docker-compose.yml" ]] || [[ ${current_etag} != "${s3_etag}" ]]; then + info "Downloading docker-compose.yml from S3" + if ! aws s3 cp "s3://${minecraft_bucket}/docker-compose.yml" "${efs_mount}/"; then + error "Failed to copy docker-compose.yml" + return 4 + fi + # Store the new ETag + echo "${s3_etag}" >"${compose_etag_file}" + info "Updated docker-compose.yml with new version" + else + info "docker-compose.yml is up to date" + fi + + # Download or update scripts directory + local scripts_etag_file="${efs_mount}/.scripts.etag" + local scripts_etag + + # Get the ETag of the scripts directory (using a manifest file or specific script) + if ! scripts_etag=$(aws s3api head-object \ + --bucket "${minecraft_bucket}" \ + --key "scripts/manifest.txt" \ + --query 'ETag' \ + --output text 2>/dev/null); then + error "Failed to get ETag for scripts manifest" + return 4 + fi + + # Remove quotes from ETag + scripts_etag=$(echo "${scripts_etag}" | tr -d '"') + local current_scripts_etag="" + + # Read current scripts ETag if it exists + if [[ -f ${scripts_etag_file} ]]; then + current_scripts_etag=$(cat "${scripts_etag_file}") + fi + + # Download if directory doesn't exist or ETag is different + if [[ ! -d "${efs_mount}/scripts" ]] || [[ ${current_scripts_etag} != "${scripts_etag}" ]]; then + info "Downloading scripts from S3" + if ! aws s3 cp "s3://${minecraft_bucket}/scripts/" "${efs_mount}/scripts/" --recursive; then + error "Failed to copy scripts" + return 4 + fi + # Make scripts executable + chmod +x "${efs_mount}/scripts/"*.py "${efs_mount}/scripts/"*.sh + # Store the new ETag + echo "${scripts_etag}" >"${scripts_etag_file}" + info "Updated scripts with new version" + else + info "Scripts are up to date" + fi + + info "Environment setup completed successfully" +} + +setup_monitoring() { + local efs_mount="${1}" + + # Create systemd service for monitoring + cat >"/etc/systemd/system/minecraft-monitor.service" < 0: + last_active_time = current_time + logger.info("Active players: %d", players) + else: + inactive_minutes = ( + current_time - last_active_time + ).total_seconds() / 60 + logger.info( + "No players online. Inactive for %.1f minutes", inactive_minutes + ) + + if inactive_minutes >= inactivity_timeout: + logger.info( + "Inactivity timeout (%d minutes) reached", + inactivity_timeout, + ) + stop_instance(ec2, instance_id) + break + + except RCONError as err: + logger.error("RCON error: %s", err) + except ClientError as err: + logger.error("AWS API error: %s", err) + except Exception as err: # pylint: disable=broad-except + logger.error("Unexpected error: %s", err) + + time.sleep(60) # Check every minute + + except (ConfigurationError, MetadataError) as err: + logger.error("Fatal error: %s", err) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/aws/website/admin-team.html b/aws/website/admin-team.html new file mode 100644 index 0000000..cbf2bc9 --- /dev/null +++ b/aws/website/admin-team.html @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + AzureMine | Admin-Team + + + + + + + + + +
+
+
+ + +
+ + +
+ + + + diff --git a/aws/website/contact.html b/aws/website/contact.html new file mode 100644 index 0000000..976d797 --- /dev/null +++ b/aws/website/contact.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + AzureMine | Contact + + + + + + + + + +
+
+

Contact form

+ +
+
+ + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + + +
+
+
+ + +
+ + +
+ + + + diff --git a/aws/website/css/global.css b/aws/website/css/global.css new file mode 100644 index 0000000..7b9130a --- /dev/null +++ b/aws/website/css/global.css @@ -0,0 +1,265 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"); /*font-family: 'Inter', sans-serif;*/ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + scroll-behavior: smooth; +} + +:root { + --main-font: "Inter", sans-serif; + --main-color: #39beff; + --background-color: #24272b; + --white-color: #ffffff; + --black-color: #000000; + --description-color: #d2d0d0; + + --green-color: #4aff6b; + --ip-copied-background: rgba(74, 255, 107, 0.17); + --ip-copied-icon-background: rgba(74, 255, 107, 0.5); + + --copy-ip-button-background: rgba(57, 190, 255, 0.7); + --how-to-join-button-background: rgba(210, 208, 208, 0.2); + --stats-background: rgba(210, 208, 208, 0.05); + --stat-icon-background-2: rgba(57, 190, 255, 0.5); + + --scroll-bar: rgba(210, 208, 208, 0.3); + --scroll-bar-hover: #555555ff; + + --red-color: #ff7c7c; + --warning-background: rgba(255, 124, 124, 0.17); + --warning-icon-background: rgba(255, 124, 124, 0.5); + --warning-color: #f5c1c1; + + /*Admin-Team rank colors*/ + --default-rank-color: rgba(210, 208, 208, 0.3); +} + +body { + background: var(--background-color); + font-family: var(--main-font); +} + +/*Navbar*/ +.navbar { + display: flex; + width: 100%; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 20px 150px; + background: var(--background-color); + transition: 0.3s ease-in-out; +} + +.navbar a { + text-decoration: none; +} + +.navbar .menu-mobile { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +.navbar .menu-mobile .logo { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + gap: 10px; +} + +.navbar .menu-mobile .logo img { + max-width: 40px; + height: auto; +} + +.navbar .menu-mobile .logo h3 { + color: var(--white-color); + font-weight: 900; + font-size: 20px; +} + +.navbar .links { + display: flex; + flex-direction: row; + gap: 30px; + transition: 0.3s ease-in-out; +} + +.navbar .links .link { + color: var(--description-color); + font-size: 18px; + font-weight: 600; + text-decoration: none; + position: relative; + transition: 0.2s; +} + +.navbar .links .link.active { + color: var(--white-color); +} + +.navbar .links .link.active::after { + content: ""; + position: absolute; + bottom: -4px; + left: 0; + width: 40px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +.navbar .links .link:not(.active):hover { + color: var(--white-color); +} + +.navbar .menu-mobile .hamburger { + color: var(--white-color); + font-size: 20px; + cursor: pointer; + transition: 0.5s; + display: none; +} + +.navbar .menu-mobile .hamburger:hover { + opacity: 0.8; +} + +.navbar.active { + max-height: 1000px; +} + +.navbar.active .links { + opacity: 1; + z-index: 2; +} + +/*Footer*/ +#footer { + padding: 20px 150px; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +#footer p { + color: var(--description-color); + font-size: 17px; +} + +#footer p span { + color: var(--white-color); +} + +#footer .social-links { + display: flex; + flex-direction: row; + gap: 15px; +} + +#footer .social-links .link { + color: var(--description-color); + text-decoration: none; + font-size: 20px; + transition: 0.2s; +} + +#footer .social-links .link:hover { + color: var(--white-color); +} + +/*Scrollbar*/ +::-webkit-scrollbar { + width: 5px; +} + +::-webkit-scrollbar-track { + background: var(--stats-background); +} + +::-webkit-scrollbar-thumb { + background: var(--scroll-bar); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--scroll-bar-hover); +} + +/*Other*/ +section:not(#header) .content { + padding: 90px 150px; + width: 100%; +} + +/*Responsive*/ +@media screen and (max-width: 1625px) { + .navbar { + padding: 20px 90px; + } +} + +@media screen and (max-width: 819px) { + .navbar { + padding: 20px 30px; + } +} + +@media screen and (max-width: 867px) { + .navbar { + flex-direction: column; + gap: 30px; + max-height: 90px; + } + + .navbar .menu-mobile { + width: 100%; + } + + .navbar .menu-mobile .hamburger { + display: flex; + } + + .navbar .links { + flex-direction: column; + order: 2; + width: 100%; + opacity: 0; + z-index: -1; + } +} + +@media screen and (max-width: 1625px) { + section:not(#header) .content { + padding: 90px; + } + + #footer { + padding: 20px 90px; + } +} + +@media screen and (max-width: 819px) { + section:not(#header) .content { + padding: 50px 30px; + } + + #footer { + padding: 20px 30px; + } +} + +@media screen and (max-width: 564px) { + #footer { + flex-direction: column; + gap: 30px; + align-items: center; + justify-content: center; + text-align: center; + } +} diff --git a/aws/website/css/pages/admin-team.css b/aws/website/css/pages/admin-team.css new file mode 100644 index 0000000..729cf15 --- /dev/null +++ b/aws/website/css/pages/admin-team.css @@ -0,0 +1,197 @@ +@import "css/global.css"; + +/*Header*/ +#header { + background: url("../../images/header-background.jpg") no-repeat fixed center; + min-width: 100%; + min-height: 400px; + height: 30vh; + background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + -webkit-background-size: cover; + z-index: 0; + display: flex; + align-items: center; +} + +#header .content { + display: flex; + flex-direction: column; + gap: 20px; + align-items: center; + padding: 150px; + width: 100%; +} + +#header .content .info { + display: flex; + flex-direction: column; +} + +#header .content .info .minecraft-server-ip { + color: var(--white-color); + text-transform: uppercase; + font-size: 18px; + font-weight: 600; +} + +#header .content .info .title { + color: var(--white-color); + text-transform: uppercase; + font-size: 65px; + font-weight: 900; +} + +#header .content .info .title span { + color: var(--main-color); +} + +#header .content .description { + color: var(--description-color); + font-size: 18px; + font-weight: 400; + max-width: 700px; + line-height: 1.6; +} + +/*Admin-Team*/ +#admin-team .content { + display: flex; + flex-direction: column; + gap: 50px; +} + +#admin-team .group { + display: flex; + flex-direction: column; + gap: 40px; +} + +#admin-team .group .rank-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#admin-team .group .rank-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#admin-team .group .rank-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#admin-team .group .users { + display: flex; + flex-direction: row; + gap: 30px; + flex-wrap: wrap; +} + +#admin-team .group .users .user { + display: flex; + flex-direction: column; + gap: 10px; + justify-content: center; + align-items: center; +} + +#admin-team .group .users .user img { + max-width: 150px; +} + +#admin-team .group .users .user .name { + color: var(--white-color); + text-transform: uppercase; + font-size: 17px; + font-weight: 700; +} + +#admin-team .group .users .user .rank { + color: var(--white-color); + font-size: 17px; + width: fit-content; + padding: 5px 10px; + text-align: center; + border-radius: 5px; +} + +.rank { + background: var(--default-rank-color); +} + +/*Footer*/ +#footer { + background: var(--stats-background); +} + +/*Responsive*/ +/*Header*/ +@media screen and (max-width: 1625px) { + #header .content { + padding: 150px 90px; + } +} + +@media screen and (max-width: 1361px) { + #header .content { + flex-direction: column; + padding: 120px 90px; + } +} + +@media screen and (max-width: 819px) { + #header .content { + padding: 150px 30px; + } + + #header .content .info .minecraft-server-ip { + font-size: 15px; + } + + #header .content .info .title { + font-size: 40px; + } + + #header .content .info .description { + font-size: 16px; + } +} + +@media screen and (max-width: 530px) { + #header .content { + justify-content: start; + align-items: start; + } + + #header .content .info .title { + font-size: 30px; + } +} + +/*Admin-Team*/ +@media screen and (max-width: 390px) { + #admin-team .group .users .user img { + max-width: 100%; + } + + #admin-team .group .users .user .name { + font-size: 20px; + } +} diff --git a/aws/website/css/pages/contact.css b/aws/website/css/pages/contact.css new file mode 100644 index 0000000..6fade62 --- /dev/null +++ b/aws/website/css/pages/contact.css @@ -0,0 +1,332 @@ +@import "css/global.css"; + +/*Header*/ +#header { + background: url("../../images/header-background.jpg") no-repeat fixed center; + min-width: 100%; + min-height: 400px; + height: 30vh; + background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + -webkit-background-size: cover; + z-index: 0; + display: flex; + align-items: center; +} + +#header .content { + display: flex; + flex-direction: column; + gap: 20px; + align-items: center; + padding: 150px; + width: 100%; +} + +#header .content .info { + display: flex; + flex-direction: column; +} + +#header .content .info .minecraft-server-ip { + color: var(--white-color); + text-transform: uppercase; + font-size: 18px; + font-weight: 600; +} + +#header .content .info .title { + color: var(--white-color); + text-transform: uppercase; + font-size: 65px; + font-weight: 900; +} + +#header .content .info .title span { + color: var(--main-color); +} + +#header .content .description { + color: var(--description-color); + font-size: 18px; + font-weight: 400; + max-width: 700px; + line-height: 1.6; +} + +/*Contacts*/ +#contacts .content { + display: flex; + flex-direction: column; + gap: 40px; +} + +#contacts .content .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#contacts .content .section-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#contacts .content .section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#contacts .content .columns { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 100px; +} + +#contacts .content .columns .contact-form { + display: flex; + flex-direction: column; + gap: 20px; + flex: 2; +} + +#contacts .content .columns .contact-form .row { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; +} + +#contacts .content .columns .contact-form .row label { + color: var(--description-color); + font-size: 15px; + line-height: 1.6; +} + +.row input { + background: var(--stats-background); + border: none; + padding: 20px 25px; + font-size: 17px; + color: var(--white-color); + outline: none; + border-radius: 5px; + transition: 0.2s; +} + +.row textarea { + background: var(--stats-background); + padding: 15px 20px; + font-size: 15px; + outline: none; + line-height: 1.6; + border-radius: 5px; + color: var(--white-color); + font-family: var(--main-font); + border: none; + transition: 0.2s; + resize: vertical; + overflow-y: auto; + min-height: 200px; + max-height: 350px; + min-width: 100px; + justify-content: center; +} + +.row input:focus, +.row textarea:focus, +.row input:hover, +.row textarea:hover { + background: var(--how-to-join-button-background); +} + +#contacts .content .columns .contact-form .form-footer { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 20px; + align-items: end; +} +#contacts .content .columns .contact-form .form-footer button { + background: var(--copy-ip-button-background); + border: 2px solid var(--main-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--white-color); + font-size: 17px; + font-weight: 500; + cursor: pointer; + transition: 0.2s; + display: flex; + gap: 10px; +} + +#contacts .content .columns .contact-form .form-footer button:hover { + opacity: 0.8; +} + +#contacts .content .columns .contact-form .form-footer .alert { + color: var(--description-color); + font-size: 15px; +} + +#contacts .content .columns .url { + text-decoration: none; + height: fit-content; +} + +#contacts .content .columns .url:hover .link .link-description .icon { + transform: scale(1.1); +} + +#contacts .content .columns .link { + display: flex; + flex-direction: column; + gap: 10px; + background: var(--stats-background); + padding: 20px; + border-radius: 5px; + max-width: 400px; +} + +#contacts .content .columns .link h5 { + color: var(--white-color); + text-transform: uppercase; + font-size: 17px; + font-weight: 700; +} + +#contacts .content .columns .link .link-description { + display: flex; + flex-direction: row; + align-items: end; + gap: 50px; +} + +#contacts .content .columns .link .link-description .description { + color: var(--description-color); + font-size: 15px; + font-weight: 400; + line-height: 1.6; +} + +#contacts + .content + .columns + .link + .link-description + .description + .discord-online-users { + color: var(--green-color); +} + +#contacts .content .columns .link .link-description .icon { + border-radius: 5px; + background: var(--stat-icon-background-2); + padding: 10px; + transition: 0.2s ease-in-out; +} + +#contacts .content .columns .link .link-description .icon i { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + font-size: 20px; + position: relative; + background: var(--main-color); + color: var(--white-color); + border-radius: 5px; +} + +/*Footer*/ +#footer { + background: var(--stats-background); +} + +/*Responsive*/ +/*Header*/ +@media screen and (max-width: 1625px) { + #header .content { + padding: 150px 90px; + } +} + +@media screen and (max-width: 1361px) { + #header .content { + flex-direction: column; + padding: 120px 90px; + } +} + +@media screen and (max-width: 819px) { + #header .content { + padding: 150px 30px; + } + + #header .content .info .minecraft-server-ip { + font-size: 15px; + } + + #header .content .info .title { + font-size: 40px; + } + + #header .content .info .description { + font-size: 16px; + } +} + +@media screen and (max-width: 530px) { + #header .content { + justify-content: start; + align-items: start; + } + + #header .content .info .title { + font-size: 30px; + } +} + +/*Contacts*/ +@media screen and (max-width: 1148px) { + #contacts .content .columns { + flex-direction: column; + } + + #contacts .content .columns .url .link { + max-width: 100%; + } + + #contacts .content .columns .link .link-description { + justify-content: space-between; + } +} + +@media screen and (max-width: 540px) { + #contacts .content .columns .contact-form .form-footer { + flex-direction: column; + align-items: start; + } + + #contacts .content .columns .contact-form .form-footer button { + width: 100%; + justify-content: center; + } +} diff --git a/aws/website/css/pages/home.css b/aws/website/css/pages/home.css new file mode 100644 index 0000000..3939ed9 --- /dev/null +++ b/aws/website/css/pages/home.css @@ -0,0 +1,832 @@ +@import "css/global.css"; + +/*Header*/ +#header { + background: url("../../images/header-background.jpg") no-repeat fixed center; + min-width: 100%; + min-height: 800px; + height: 80vh; + background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + -webkit-background-size: cover; + z-index: 0; + display: flex; + align-items: center; +} + +#header .content { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 150px; + width: 100%; + height: 620px; + position: relative; +} + +#header .content .left .ip-copied { + color: var(--green-color); + background: var(--ip-copied-background); + padding: 10px 20px; + width: fit-content; + border-radius: 5px; + font-size: 17px; + display: none; +} + +#header .content .left .ip-copied.active { + display: flex; +} + +#header .content .left .ip-copied.error { + background: var(--warning-background); + color: var(--red-color); +} + +#header .content .left { + display: flex; + flex-direction: column; + gap: 20px; +} + +#header .content .left .server-name { + display: flex; + flex-direction: column; +} + +#header .content .left .server-name p { + color: var(--white-color); + text-transform: uppercase; + font-size: 18px; + font-weight: 600; +} + +#header .content .left .server-name h1 { + color: var(--main-color); + text-transform: uppercase; + font-size: 65px; + font-weight: 900; +} + +#header .content .left .server-description { + color: var(--description-color); + font-size: 18px; + font-weight: 400; + max-width: 700px; + line-height: 1.6; +} + +#header .content .left .buttons { + display: flex; + flex-direction: row; + gap: 20px; +} + +#header .content .left .buttons .copy-ip { + background: var(--copy-ip-button-background); + border: 2px solid var(--main-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--white-color); + font-size: 17px; + font-weight: 600; + cursor: pointer; + transition: 0.2s; +} + +#header .content .left .buttons .copy-ip:hover { + opacity: 0.8; +} + +#header .content .left .buttons .how-to-join { + background: var(--how-to-join-button-background); + border: 2px solid var(--description-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--description-color); + font-size: 17px; + font-weight: 600; + cursor: pointer; + transition: 0.2s; + width: 100%; +} + +#header .content .left .buttons .how-to-join:hover { + opacity: 0.8; +} + +#header .content .right { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; +} + +#header .content .right .logo-img { + max-width: 150px; + transform: translatey(0px); + animation: float 4s ease-in-out infinite; +} + +#header .content .right .stats { + display: flex; + flex-direction: row; + gap: 50px; + background: var(--stats-background); + padding: 20px; + border-radius: 5px; +} + +#header .content .right .stats .stat { + display: flex; + flex-direction: row; + gap: 15px; + justify-content: center; + align-items: center; +} + +#header .content .right .stats .stat .icon { + border-radius: 5px; + background: var(--stat-icon-background-2); + padding: 10px; + transition: 0.2s ease-in-out; +} + +#header .content .right .stats .stat .icon i { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + font-size: 20px; + position: relative; + background: var(--main-color); + color: var(--white-color); + border-radius: 5px; +} + +#header .content .right .stats .stat:hover .icon { + transform: scale(1.1); +} + +#header .content .right .stats .stat .texts { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +#header .content .right .stats .stat .texts h5 { + color: var(--white-color); + text-transform: uppercase; + font-size: 17px; + font-weight: 700; +} + +#header .content .right .stats .stat .texts p { + color: var(--description-color); + font-size: 15px; + font-weight: 400; +} + +#header .content .right .stats .stat .texts p span { + color: var(--green-color); +} + +/*About*/ +#about .content { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +} + +#about .content .left { + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; + gap: 40px; +} + +#about .content .left .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#about .content .left .section-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#about .content .left .section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#about .content .left .about-us { + color: var(--description-color); + font-size: 17px; + max-width: 80%; + line-height: 1.6; +} + +#about .content .right { + display: flex; + justify-content: end; + align-items: center; + height: 100%; + position: relative; + width: 100%; +} + +#about .content .right img { + width: auto; + max-height: 250px; + position: relative; + z-index: 1; + right: 100px; +} + +#about .content .right .img-background { + position: absolute; + width: 210px; + height: calc(100% - 40px); + border-radius: 40px 3px; + background: var(--main-color); + z-index: 0; + bottom: 5px; +} + +/*Mini games*/ +#minigames { + background: var(--stats-background); +} + +#minigames .content { + display: flex; + flex-direction: column; + gap: 60px; +} + +#minigames .content .game { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-between; + align-items: center; + gap: 60px; +} + +#minigames .content .game:nth-child(even) img { + order: 1; +} + +#minigames .content .game:nth-child(even) img:hover { + transform: rotate(2deg); +} + +#minigames .content .game img { + width: 100%; + height: 300px; + object-fit: cover; + border-radius: 40px 3px; + transition: 0.2s; +} + +#minigames .content .game img:hover { + transform: rotate(-1deg); +} + +#minigames .content .game .info { + display: flex; + flex-direction: column; + gap: 40px; +} + +#minigames .content .game .info .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#minigames .content .game .info .section-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#minigames .content .game .info .section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#minigames .content .game .info .game-description { + color: var(--description-color); + font-size: 17px; + line-height: 1.6; +} + +#minigames .content .game .info .game-description ul { + padding: 10px 0 0 40px; +} + +/*Discord*/ +#discord { + background: url("../../images/header-background.jpg") no-repeat fixed center; + min-width: 100%; + height: 100%; + background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + -webkit-background-size: cover; + z-index: 0; + display: flex; + align-items: center; +} + +#discord .content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 40px; +} + +#discord .content .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; + text-align: center; +} + +#discord .content .section-title span { + color: var(--main-color); +} + +#discord .content .join-discord { + background: var(--copy-ip-button-background); + border: 2px solid var(--main-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--white-color); + font-size: 17px; + font-weight: 600; + cursor: pointer; + transition: 0.2s; +} + +#discord .content .join-discord:hover { + opacity: 0.8; +} + +#discord .content .join-discord a { + text-decoration: none; + color: var(--white-color); +} + +/*Vote*/ +#vote .content { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +} + +#vote .content .info { + display: flex; + flex-direction: column; + gap: 40px; + flex: 1; +} + +#vote .content .info .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#vote .content .info .section-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#vote .content .info .section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#vote .content .info .section-description { + color: var(--description-color); + font-size: 17px; + max-width: 80%; + line-height: 1.6; +} + +#vote .content .links { + display: flex; + flex-direction: row; + gap: 30px; + align-items: center; +} + +#vote .content .links .url { + text-decoration: none; +} + +#vote .content .links .url:hover .link .link-description .icon { + transform: scale(1.1); +} + +#vote .content .links .link { + display: flex; + flex-direction: column; + gap: 10px; + background: var(--stats-background); + padding: 20px; + border-radius: 5px; + max-width: 400px; +} + +#vote .content .links .link h5 { + color: var(--white-color); + text-transform: uppercase; + font-size: 17px; + font-weight: 700; +} + +#vote .content .links .link .link-description { + display: flex; + flex-direction: row; + align-items: center; + gap: 50px; +} + +#vote .content .links .link .link-description .description { + color: var(--description-color); + font-size: 15px; + font-weight: 400; + line-height: 1.6; +} + +#vote .content .links .link .link-description .icon { + border-radius: 5px; + background: var(--stat-icon-background-2); + padding: 10px; + transition: 0.2s ease-in-out; +} + +#vote .content .links .link .link-description .icon i { + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + font-size: 20px; + position: relative; + background: var(--main-color); + color: var(--white-color); + border-radius: 5px; +} + +/*FAQ*/ +#faq { + background: var(--stats-background); +} + +#faq .content { + display: flex; + flex-direction: column; + gap: 20px; +} + +#faq .content .info { + display: flex; + flex-direction: column; + gap: 40px; +} + +#faq .content .info .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#faq .content .info .section-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#faq .content .info .section-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#faq .content .info .section-title span { + color: var(--main-color); +} + +#faq .content .info .section-description { + color: var(--description-color); + font-size: 17px; + line-height: 1.6; + max-width: 70%; +} + +#faq .content .accordion { + display: flex; + flex-direction: column; + gap: 20px; +} + +#faq .content .accordion .accordion-item { + background: var(--stats-background); + border-radius: 5px; +} + +#faq .content .accordion .accordion-item .accordion-item-header { + padding: 20px 50px 20px 20px; + line-height: 1.6; + font-weight: 600; + display: flex; + align-items: center; + position: relative; + cursor: pointer; + color: var(--white-color); + font-size: 17px; +} + +#faq .content .accordion .accordion-item .accordion-item-header::after { + content: "\002B"; + font-size: 20px; + position: absolute; + right: 20px; +} + +#faq .content .accordion .accordion-item .accordion-item-header.active::after { + content: "\2212"; +} + +#faq .content .accordion .accordion-item .accordion-item-body { + max-height: 0; + overflow: hidden; + transition: 0.2s ease-in-out; +} + +#faq + .content + .accordion + .accordion-item + .accordion-item-body + .accordion-item-body-content { + padding: 20px; + line-height: 1.6; + border-top: 1px solid var(--stats-background); + color: var(--description-color); +} + +/*Animations*/ +@keyframes float { + 0% { + transform: translatey(0px); + } + 50% { + transform: translatey(-20px); + } + 100% { + transform: translatey(0px); + } +} + +/*Responsive*/ +/*Header*/ +@media screen and (max-width: 1625px) { + #header .content { + padding: 150px 90px; + align-items: start; + justify-content: center; + } +} + +@media screen and (max-width: 1361px) { + #header .content { + flex-direction: column; + padding: 120px 90px; + height: 100%; + gap: 60px; + } + + #header .content .left { + gap: 30px; + justify-content: left; + } + + #header .content .right .stats { + width: fit-content; + } + + #header .content .right .logo-img { + display: none; + } + + @media screen and (min-height: 745px) { + #header .content { + justify-content: center; + } + + #header .content .left { + height: fit-content; + justify-content: center; + gap: 30px; + } + + #header .content .right { + height: fit-content; + justify-content: center; + gap: 30px; + } + } +} + +@media screen and (max-width: 819px) { + #header .content { + padding: 150px 30px; + } + + #header .content .left .server-name p { + font-size: 15px; + } + + #header .content .left .server-name h1 { + font-size: 40px; + } + + #header .content .left .server-description { + font-size: 16px; + } + + #header .content .right { + width: 100%; + } + + #header .content .right .stats { + width: 100%; + justify-content: space-between; + } +} + +@media screen and (max-width: 621px) { + #header .content .right .stats { + width: 100%; + flex-direction: column; + justify-content: space-between; + align-items: start; + } + + #header .content .right .stats .stat:hover .icon { + transform: scale(1); + } +} + +@media screen and (max-width: 447px) { + #header .content .left .server-name h1 { + font-size: 30px; + } +} + +@media screen and (max-width: 383px) { + #header .content .left .buttons { + flex-direction: column; + } +} + +/*About us*/ +@media screen and (max-width: 1551px) { + #about .content .left .about-us { + max-width: 90%; + } +} + +@media screen and (max-width: 1183px) { + #about .content { + flex-direction: column; + gap: 40px; + } + + #about .content .left .about-us { + max-width: 100%; + } + + #about .content .right img { + margin: auto; + right: 0; + } + + #about .content .right .img-background { + width: 100%; + } +} + +/*Mini games*/ +@media screen and (max-width: 1141px) { + #minigames .content .game { + flex-direction: column; + gap: 40px; + } + + #minigames .content .game:nth-child(even) img { + order: 0; + } +} + +/*Vote*/ +@media screen and (max-width: 1313px) { + #vote .content .links { + flex-direction: column; + } +} + +@media screen and (max-width: 909px) { + #vote .content .links { + flex-wrap: wrap; + flex-direction: row; + } + + #vote .content .links .link { + max-width: 300px; + } + + #vote .content { + flex-direction: column; + gap: 40px; + } +} + +@media screen and (max-width: 690px) { + #vote .content .links .link { + max-width: 100%; + } +} + +/*FAQ*/ +@media screen and (max-width: 6729px) { + #faq .content .info .section-description { + max-width: 100%; + } +} diff --git a/aws/website/css/pages/rules.css b/aws/website/css/pages/rules.css new file mode 100644 index 0000000..40f2441 --- /dev/null +++ b/aws/website/css/pages/rules.css @@ -0,0 +1,298 @@ +@import "css/global.css"; + +/*Header*/ +#header { + background: url("../../images/header-background.jpg") no-repeat fixed center; + min-width: 100%; + min-height: 400px; + height: 30vh; + background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + -webkit-background-size: cover; + z-index: 0; + display: flex; + align-items: center; +} + +#header .content { + display: flex; + flex-direction: column; + gap: 20px; + align-items: center; + padding: 150px; + width: 100%; +} + +#header .content .info { + display: flex; + flex-direction: column; +} + +#header .content .info .minecraft-server-ip { + color: var(--white-color); + text-transform: uppercase; + font-size: 18px; + font-weight: 600; +} + +#header .content .info .title { + color: var(--white-color); + text-transform: uppercase; + font-size: 65px; + font-weight: 900; +} + +#header .content .info .title span { + color: var(--main-color); +} + +#header .content .description { + color: var(--description-color); + font-size: 18px; + font-weight: 400; + max-width: 700px; + line-height: 1.6; +} + +/*Rules*/ +#rules .content { + display: flex; + flex-direction: column; + gap: 50px; +} + +#rules .warning { + background: var(--warning-background); + padding: 20px; + border-radius: 5px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} + +#rules .warning p { + color: var(--warning-color); + font-size: 17px; +} + +#rules .warning .icon { + border-radius: 5px; + background: var(--warning-icon-background); + padding: 10px; + transition: 0.2s ease-in-out; +} + +#rules .warning .icon i { + display: flex; + justify-content: center; + align-items: center; + width: 30px; + height: 30px; + font-size: 20px; + position: relative; + background: var(--red-color); + color: var(--white-color); + border-radius: 5px; +} + +#rules .warning:hover .icon { + transform: scale(1.1); +} + +#rules .rules { + display: flex; + flex-direction: column; + gap: 40px; +} + +#rules .rules .rules-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; +} + +#rules .rules .rules-title::before { + content: ""; + position: absolute; + bottom: -9px; + left: 0; + width: 150px; + height: 1px; + border-radius: 5px; + background: var(--description-color); +} + +#rules .rules .rules-title::after { + content: ""; + position: absolute; + bottom: -10px; + left: 0; + width: 50px; + height: 3px; + border-radius: 5px; + background: var(--main-color); +} + +#rules .rules .rules-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-left: 40px; +} + +#rules .rules .rules-list .rule { + color: var(--description-color); + font-size: 17px; + line-height: 1.6; +} + +#rules .rules .rules-list .rule span { + color: var(--main-color); +} + +/*Server Join*/ +#join-server { + background: var(--stats-background); + min-width: 100%; + height: 100%; + display: flex; + align-items: center; +} + +#join-server .content { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 40px; +} + +#join-server .content .section-title { + font-size: 30px; + font-weight: 700; + color: var(--white-color); + position: relative; + text-align: center; +} + +#join-server .content .section-title span { + color: var(--main-color); +} + +#join-server .content .buttons { + display: flex; + flex-direction: row; + gap: 20px; +} + +#join-server .content .buttons .copy-ip { + background: var(--copy-ip-button-background); + border: 2px solid var(--main-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--white-color); + font-size: 17px; + font-weight: 600; + cursor: pointer; + transition: 0.2s; +} + +#join-server .content .buttons .join-discord { + background: var(--how-to-join-button-background); + border: 2px solid var(--description-color); + border-radius: 3px; + padding: 10px 30px; + color: var(--description-color); + font-size: 17px; + font-weight: 600; + cursor: pointer; + transition: 0.2s; +} + +#join-server .content .buttons .copy-ip:hover, +#join-server .content .buttons .join-discord:hover { + opacity: 0.8; +} + +#join-server .content .ip-copied { + color: var(--green-color); + background: var(--ip-copied-background); + padding: 10px 20px; + width: fit-content; + border-radius: 5px; + font-size: 17px; + display: none; +} + +#join-server .content .ip-copied.active { + display: flex; +} + +#join-server .content .ip-copied.error { + background: var(--warning-background); + color: var(--red-color); +} + +/*Responsive*/ +/*Header*/ +@media screen and (max-width: 1625px) { + #header .content { + padding: 150px 90px; + } +} + +@media screen and (max-width: 1361px) { + #header .content { + flex-direction: column; + padding: 120px 90px; + } +} + +@media screen and (max-width: 819px) { + #header .content { + padding: 150px 30px; + } + + #header .content .info .minecraft-server-ip { + font-size: 15px; + } + + #header .content .info .title { + font-size: 40px; + } + + #header .content .info .description { + font-size: 16px; + } +} + +@media screen and (max-width: 530px) { + #header .content { + justify-content: start; + align-items: start; + } + + #header .content .info .title { + font-size: 30px; + } +} + +/*Rules*/ +@media screen and (max-width: 462px) { + #rules .warning { + flex-direction: column; + gap: 20px; + text-align: center; + } +} + +/*Server Join*/ +@media screen and (max-width: 380px) { + #join-server .content .buttons { + flex-direction: column; + width: 100%; + } +} diff --git a/aws/website/images/about-section-person-image.png b/aws/website/images/about-section-person-image.png new file mode 100644 index 0000000..b2e0a8a Binary files /dev/null and b/aws/website/images/about-section-person-image.png differ diff --git a/aws/website/images/discord-section-background.jpg b/aws/website/images/discord-section-background.jpg new file mode 100644 index 0000000..5b04342 Binary files /dev/null and b/aws/website/images/discord-section-background.jpg differ diff --git a/aws/website/images/header-background.jpg b/aws/website/images/header-background.jpg new file mode 100644 index 0000000..78aef7f Binary files /dev/null and b/aws/website/images/header-background.jpg differ diff --git a/aws/website/images/logo.png b/aws/website/images/logo.png new file mode 100644 index 0000000..7577ea1 Binary files /dev/null and b/aws/website/images/logo.png differ diff --git a/aws/website/images/survival-minigames-image.jpg b/aws/website/images/survival-minigames-image.jpg new file mode 100644 index 0000000..c3d7356 Binary files /dev/null and b/aws/website/images/survival-minigames-image.jpg differ diff --git a/aws/website/index.html b/aws/website/index.html new file mode 100644 index 0000000..157bf7b --- /dev/null +++ b/aws/website/index.html @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + AzureMine | Home + + + + + + + + + +
+
+
+

Who we are?

+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. Mikroktig + dovis preliga, ogt. Dihagen säs såvis nun. Dra åt helvete-kapital + sana inklusive orat i kock för realingar. Biledes. Antik möligt. + Antev jyling astropp. Antroponetik förstärkt verklighet, om + curlingförälder. Andronetik bityr. Mivoling. Dov vygåd sarat megara. + Tematisk intras då töning. Koka böcker debev. Mivoling. Dov vygåd + sarat megara. Tematisk intras då töning. Koka böcker debev. + Mivoling. Dov vygåd sarat megara. Tematisk intras då töning. Koka + böcker +

+
+ +
+ Minecraft person +
+
+
+
+ + +
+
+
+ Survival image + +
+

Survival

+
+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. + Mikroktig dovis preliga, ogt. Dihagen säs såvis nun. Dra åt + helvete-kapital sana +

+
    +
  • Economy
  • +
  • SlimeFun
  • +
  • Player Warps
  • +
  • and more
  • +
+
+
+
+ +
+ Survival image + +
+

SkyBlock

+
+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. + Mikroktig dovis preliga, ogt. Dihagen säs såvis nun. Dra åt + helvete-kapital sana +

+
    +
  • Economy
  • +
  • SlimeFun
  • +
  • Player Warps
  • +
  • and more
  • +
+
+
+
+
+
+ + +
+
+

+ Have a problem or want to get in touch with others? +
+ Join our Discord! +

+ +
+
+ + +
+
+
+

Why vote?

+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. Mikroktig + dovis preliga, ogt. Dihagen säs såvis nun. Dra åt helvete +

+
+ + +
+
+ + +
+
+
+

Server FAQs.

+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. Mikroktig + dovis preliga, ogt. Dihagen säs såvis nun. Dra åt helvete +

+
+ +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+
+
+
+ + +
+ + +
+ + + + diff --git a/aws/website/js/script.js b/aws/website/js/script.js new file mode 100644 index 0000000..252846b --- /dev/null +++ b/aws/website/js/script.js @@ -0,0 +1,393 @@ +/* +Configuration +------------------------ +If something doesn't work please contact me on discord (Astronawta#0012). +*/ + +const config = { + serverInfo: { + serverLogoImageFileName: + "logo.png" /*This is a file name for logo in /images/ (If you upload new logo with other name, you must change this value)*/, + serverName: "ExampleName" /*Server name*/, + serverIp: + "mc.hypixel.net" /*Server IP (if you want to add online user counter, you must have true the enable-status and enable-query of server.properties)*/, + discordServerID: + "489529070913060867" /*Your server ID (if you want to add online user counter, you must have enabled Discord server widget)*/, + }, + + /*Admin-Team + ------------ + If you want to create new group, you must add this structure to adminTeamPage: + : [ + { + inGameName: "Astronavta", + rank: "Owner", + skinUrlOrPathToFile: "", + rankColor: "" + }, + ] + then you must add this group with same name to atGroupsDefaultColors and set the color you want for the group. + You can also set a special color for a specific user, just put it in the rankColor of that user. + + All skins for original players are generate automaticaly. If you want to add skins to warez players, yout must add url for skin to skinUrlOrPathToFile + { + inGameName: "Astronavta", <--- In-Game name + rank: "Owner", <-- rank + skinUrlOrPathToFile: "", <-- url or file path for skin image for warez players (if you have original minecraft leave it be empty) + rankColor: "rgba(255, 3, 3, 1)" <-- special rank color + }, + + If you want to change skin type replace userSKinTypeInAdminTeam with something you want from array in comments + */ + userSKinTypeInAdminTeam: + "bust" /*[full, bust, head, face, front, frontFull, skin]*/, + atGroupsDefaultColors: { + leaders: "rgba(255, 124, 124, 0.5)", + developers: "rgba(230, 83, 0, 0.5)", + helpers: "rgba(11, 175, 255, 0.5)", + builders: "rgba(247, 2, 176, 0.5)", + }, + adminTeamPage: { + leaders: [ + { + inGameName: "Astronavta", + rank: "Owner", + skinUrlOrPathToFile: "", + rankColor: "rgba(255, 3, 3, 1)", + }, + { + inGameName: "Astronavta", + rank: "Owner", + skinUrlOrPathToFile: "", + rankColor: "rgba(255, 3, 3, 1)", + }, + { + inGameName: "Astronavta", + rank: "Manager", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Moderator", + skinUrlOrPathToFile: "", + rankColor: "", + }, + ], + developers: [ + { + inGameName: "Astronavta", + rank: "Developer", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Developer", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Webmaster", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Discord manager", + skinUrlOrPathToFile: "", + rankColor: "", + }, + ], + helpers: [ + { + inGameName: "Astronavta", + rank: "Helper++", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Helper++", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Helper+", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Helper+", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Helper", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Helper", + skinUrlOrPathToFile: "", + rankColor: "", + }, + ], + builders: [ + { + inGameName: "Astronavta", + rank: "Builder++", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Builder++", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Builder+", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Builder+", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Builder", + skinUrlOrPathToFile: "", + rankColor: "", + }, + { + inGameName: "Astronavta", + rank: "Builder", + skinUrlOrPathToFile: "", + rankColor: "", + }, + ], + }, + + /* + Contact form + ------------ + To activate, you need to send the first email via the contact form and confirm it in the email. + Emails are sent via https://formsubmit.co/ + */ + contactPage: { + email: "astronavta@example.com", + }, +}; + +/*If you want to change website color go to /css/global.css and in :root {} is a color pallete (don't change names of variables, change only values)*/ + +/*If you want everything to work as it should and you don't understand what is written here, don't touch it :D*/ + +/*Mobile navbar (open, close)*/ +const navbar = document.querySelector(".navbar"); +const navbarLinks = document.querySelector(".links"); +const hamburger = document.querySelector(".hamburger"); + +hamburger.addEventListener("click", () => { + navbar.classList.toggle("active"); + navbarLinks.classList.toggle("active"); +}); + +/*FAQs*/ +const accordionItemHeaders = document.querySelectorAll( + ".accordion-item-header", +); + +accordionItemHeaders.forEach((accordionItemHeader) => { + accordionItemHeader.addEventListener("click", () => { + accordionItemHeader.classList.toggle("active"); + const accordionItemBody = accordionItemHeader.nextElementSibling; + + if (accordionItemHeader.classList.contains("active")) + accordionItemBody.style.maxHeight = accordionItemBody.scrollHeight + "px"; + else accordionItemBody.style.maxHeight = "0px"; + }); +}); + +/*Config navbar*/ +const serverName = document.querySelector(".server-name"); +const serverLogo = document.querySelector(".logo-img"); +/*Config header*/ +const serverIp = document.querySelector(".minecraft-server-ip"); +const serverLogoHeader = document.querySelector(".logo-img-header"); +const discordOnlineUsers = document.querySelector(".discord-online-users"); +const minecraftOnlinePlayers = document.querySelector( + ".minecraft-online-players", +); +/*Config contact*/ +const contactForm = document.querySelector(".contact-form"); +const inputWithLocationAfterSubmit = document.querySelector( + ".location-after-submit", +); + +const getDiscordOnlineUsers = async () => { + try { + const discordServerId = config.serverInfo.discordServerID; + + const apiWidgetUrl = `https://discord.com/api/guilds/${discordServerId}/widget.json`; + let response = await fetch(apiWidgetUrl); + let data = await response.json(); + + if (!data.presence_count) return "None"; + else return await data.presence_count; + } catch (e) { + return "None"; + } +}; + +const getMinecraftOnlinePlayer = async () => { + try { + const serverIp = config.serverInfo.serverIp; + + const apiUrl = `https://api.mcsrvstat.us/2/${serverIp}`; + let response = await fetch(apiUrl); + let data = await response.json(); + + return data.players.online; + } catch (e) { + console.log(e); + return "None"; + } +}; + +const getUuidByUsername = async (username) => { + try { + const usernameToUuidApi = `https://api.minetools.eu/uuid/${username}`; + let response = await fetch(usernameToUuidApi); + let data = await response.json(); + + return data.id; + } catch (e) { + console.log(e); + return "None"; + } +}; + +const getSkinByUuid = async (username) => { + try { + const skinByUuidApi = `https://visage.surgeplay.com/${config.userSKinTypeInAdminTeam}/512/${await getUuidByUsername(username)}`; + let response = await fetch(skinByUuidApi); + + if (response.status === 400) + return `https://visage.surgeplay.com/${config.userSKinTypeInAdminTeam}/512/ec561538f3fd461daff5086b22154bce`; + else return skinByUuidApi; + } catch (e) { + console.log(e); + return "None"; + } +}; + +/*IP copy only works if you have HTTPS on your website*/ +const copyIp = () => { + const copyIpButton = document.querySelector(".copy-ip"); + const copyIpAlert = document.querySelector(".ip-copied"); + + copyIpButton.addEventListener("click", () => { + try { + navigator.clipboard.writeText(config.serverInfo.serverIp); + + copyIpAlert.classList.add("active"); + + setTimeout(() => { + copyIpAlert.classList.remove("active"); + }, 5000); + } catch (e) { + console.log(e); + copyIpAlert.innerHTML = "An error has occurred!"; + copyIpAlert.classList.add("active"); + copyIpAlert.classList.add("error"); + + setTimeout(() => { + copyIpAlert.classList.remove("active"); + copyIpAlert.classList.remove("error"); + }, 5000); + } + }); +}; + +const setDataFromConfigToHtml = async () => { + /*Set config data to navbar*/ + serverName.innerHTML = config.serverInfo.serverName; + serverLogo.src = `images/` + config.serverInfo.serverLogoImageFileName; + + /*Set config data to header*/ + serverIp.innerHTML = config.serverInfo.serverIp; + + let locationPathname = location.pathname; + + if (locationPathname == "/" || locationPathname.includes("index")) { + copyIp(); + /*Set config data to header*/ + serverLogoHeader.src = + `images/` + config.serverInfo.serverLogoImageFileName; + discordOnlineUsers.innerHTML = await getDiscordOnlineUsers(); + minecraftOnlinePlayers.innerHTML = await getMinecraftOnlinePlayer(); + } else if (locationPathname.includes("rules")) { + copyIp(); + } else if (locationPathname.includes("admin-team")) { + for (let team in config.adminTeamPage) { + const atContent = document.querySelector(".at-content"); + + const group = document.createElement("div"); + group.classList.add("group"); + group.classList.add(team); + + const groupSchema = ` +

${team.charAt(0).toUpperCase() + team.slice(1)}

+
+
+ `; + + group.innerHTML = groupSchema; + + atContent.appendChild(group); + + for (let j = 0; j < config.adminTeamPage[team].length; j++) { + let user = config.adminTeamPage[team][j]; + const group = document.querySelector("." + team + " .users"); + + const userDiv = document.createElement("div"); + userDiv.classList.add("user"); + + let userSkin = config.adminTeamPage[team][j].skinUrlOrPathToFile; + + if (userSkin == "") userSkin = await getSkinByUuid(user.inGameName); + let rankColor = config.atGroupsDefaultColors[team]; + + if (user.rankColor != "") { + rankColor = user.rankColor; + } + + const userDivSchema = ` + ${user.inGameName} +
${user.inGameName}
+

${user.rank}

+ `; + + userDiv.innerHTML = userDivSchema; + group.appendChild(userDiv); + } + } + } else if (locationPathname.includes("contact")) { + contactForm.action = `https://formsubmit.co/${config.contactPage.email}`; + discordOnlineUsers.innerHTML = await getDiscordOnlineUsers(); + inputWithLocationAfterSubmit.value = location.href; + } +}; + +setDataFromConfigToHtml(); diff --git a/aws/website/rules.html b/aws/website/rules.html new file mode 100644 index 0000000..6d9afbe --- /dev/null +++ b/aws/website/rules.html @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + AzureMine | Rules + + + + + + + + + +
+
+
+

Ignorance of the rules is no excuse!

+
+ +
+
+ +
+

Minecraft server

+
    +
  1. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  2. +
  3. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  4. +
  5. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  6. +
  7. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  8. +
  9. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  10. +
  11. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  12. +
  13. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  14. +
  15. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  16. +
+
+ +
+

Discord server

+
    +
  1. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  2. +
  3. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  4. +
  5. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  6. +
  7. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  8. +
  9. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  10. +
  11. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  12. +
  13. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  14. +
  15. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  16. +
+
+ +
+

More rules

+
    +
  1. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  2. +
  3. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  4. +
  5. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  6. +
  7. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  8. +
  9. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  10. +
  11. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  12. +
  13. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  14. +
  15. + Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. +
  16. +
+
+
+
+ + +
+
+

+ If you have read the rules, you can go and + enjoy the fun on our server! +

+
+ + +
+

IP was successfully copied!

+
+
+ + +
+ + +
+ + + + diff --git a/docker-compose.yaml b/docker-compose.yaml index 9671842..f5dbf12 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,6 +19,7 @@ services: - '25565:25565' # - '24454:24454/udp' environment: + CF_API_KEY: '$$2a$$10$$z5MAQHO9wcAfcl8DQ0CguegNoILh2D/xeAqFwQjjNuCwJHB8ik3bq' DIFFICULTY: 'hard' ENABLE_JMX: 'TRUE' ENABLE_ROLLING_LOGS: 'TRUE' @@ -26,20 +27,24 @@ services: EULA: "TRUE" INIT_MEMORY: '1G' MAX_MEMORY: '8G' - MODRINTH_LOADER: 'FABRIC' - MODRINTH_PROJECT: 'allexio-create+' - MODRINTH_VERSION: '1.0.0' - MOTD: "Allexio's Create Plus" + TYPE: 'AUTO_CURSEFORGE' + CF_SLUG: 'forge-frontier' + CF_PAGE_URL: 'https://www.curseforge.com/minecraft/modpacks/forge-frontier/' + # TYPE: 'MODRINTH' + # MODRINTH_LOADER: 'FABRIC' + # MODRINTH_PROJECT: 'allexio-create+' + # MODRINTH_VERSION: '1.0.0' + MOTD: "Create - Forge Frontier 2.1.1" OPS: "egut" SEED: '-6337350249272507649' - SERVER_NAME: "Allexio's Create Plus" + SERVER_NAME: "Rubik Create" SERVER_PORT: 25565 SIMULATION_DISTANCE: 16 - TYPE: 'MODRINTH' USE_AIKAR_FLAGS: 'TRUE' VANILLATWEAKS_SHARECODE: 'ilFuDN' VERSION: "1.20.1" VIEW_DISTANCE: 24 + tty: true stdin_open: true restart: 'unless-stopped' @@ -237,7 +242,7 @@ services: depends_on: - 'grafana' - - 'bluemap' + # - 'bluemap' networks: - 'web' diff --git a/minecraft/mc-server/mods/BlueMap-3.20-fabric-1.20.jar b/minecraft/mc-server/mods/BlueMap-3.20-fabric-1.20.jar deleted file mode 100644 index eb5c439..0000000 Binary files a/minecraft/mc-server/mods/BlueMap-3.20-fabric-1.20.jar and /dev/null differ diff --git a/minecraft/mc-server/mods/BlueMap-5.3-forge-1.20.jar b/minecraft/mc-server/mods/BlueMap-5.3-forge-1.20.jar new file mode 100644 index 0000000..081525e Binary files /dev/null and b/minecraft/mc-server/mods/BlueMap-5.3-forge-1.20.jar differ diff --git a/minecraft/mc-server/mods/Prometheus-Exporter-1.20.1-forge-1.0.0.jar b/minecraft/mc-server/mods/Prometheus-Exporter-1.20.1-forge-1.0.0.jar new file mode 100644 index 0000000..1cad25b Binary files /dev/null and b/minecraft/mc-server/mods/Prometheus-Exporter-1.20.1-forge-1.0.0.jar differ diff --git a/minecraft/mc-server/mods/fabricexporter-1.0.10.jar b/minecraft/mc-server/mods/fabricexporter-1.0.10.jar deleted file mode 100644 index e96082c..0000000 Binary files a/minecraft/mc-server/mods/fabricexporter-1.0.10.jar and /dev/null differ diff --git a/minecraft/mc-server/mods/spark-1.10.54-fabric.jar b/minecraft/mc-server/mods/spark-1.10.54-fabric.jar deleted file mode 100644 index f329c51..0000000 Binary files a/minecraft/mc-server/mods/spark-1.10.54-fabric.jar and /dev/null differ diff --git a/services/nginx/html/index.html b/services/nginx/html/index.html index 39a040a..7bbbb05 100644 --- a/services/nginx/html/index.html +++ b/services/nginx/html/index.html @@ -1,225 +1,310 @@ - - - - - - - - - - - - - - - - - Allexio's Create Plus | Home - - - - - - - - - -
-
-
-

Who am I?

-

I am a computer nerd that that have played Minecraft on an of since 2015. I love setting up infrastructure and that's why I setup this some what extensive server configuration.

-
- -
- Minecraft person -
-
-
-
- -
-
-
- Survival image - -
-

Allexio's Create Plus

-
-

A Create-centered modpack made for me and a bunch of friends (and friends' friends).

- -

We started with a vanilla+ base with Create as a "central" mod and a bunch of performance / cosmetic / QoL mods without changing the content much (except for Create).

- -

We then added a few vanilla-friendly content packs to keep the game fresh for returning players.

- -

In the end this will be a vanilla++ Fabric modpack that satisfies most people who want more of the latest version of Minecraft & Create without adding anything that detracts from the original experience.

-
- -
-

How to join?

-
-

- 1. Install Minecraft Launcher Modrinth
- - 2. Download Allexio's Create Plus
- - 3. Ask Egut on Discord to be white listed.
- - 4. Have fun and behave!
-

-
-
-
-
-
- - -
-
-

Have a problem or want to get in touch with others?
Join our Discord!

- -
-
- - - -
-
-
-
- Simple server setup -
-
-

Server Setup.

-

 

-

Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. Mikroktig dovis preliga, ogt. Dihagen säs såvis nun. Dra åt helvete

-
-
- -
-
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure? -
-
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure. Klittra tidögt och soment, viradade utivis. Sebina hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. Lörem -
-
-
- -
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure? -
-
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure. Klittra tidögt och soment, viradade utivis. Sebina hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. Lörem -
-
-
- -
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure? -
-
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure. Klittra tidögt och soment, viradade utivis. Sebina hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. Lörem -
-
-
- -
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure? -
-
-
- Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall sar pure. Klittra tidögt och soment, viradade utivis. Sebina hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. Lörem -
-
-
-
-
-
- - -
- - -
- - - - + + + + + + + + + + + + + + + + Rubik Create | Home + + + + + + + + + +
+
+
+

Who am I?

+

+ I am a computer nerd that that have played Minecraft on an of since + 2015. I love setting up infrastructure and that's why I setup this + some what extensive server configuration. +

+
+ +
+ Minecraft person +
+
+
+
+ +
+
+
+ Survival image + +
+

Rubik Create

+
+

+ A Create-centered modpack made for me and a bunch of friends + (and friends' friends). +

+

+ +

+ We started with a vanilla+ base with Create as a "central" mod + and a bunch of performance / cosmetic / QoL mods without + changing the content much (except for Create). +

+ +

+ We then added a few vanilla-friendly content packs to keep the + game fresh for returning players. +

+ +

+ In the end this will be a vanilla++ Fabric modpack that + satisfies most people who want more of the latest version of + Minecraft & Create without adding anything that detracts from + the original experience. +

+
+
+

How to join?

+
+

+ 1. Install Minecraft Launcher + Curseforge
+ + 2. Download + Create - Forge Frontier
+ + 3. Ask Egut on Discord to be white listed.
+ + 4. Have fun and behave!
+

+
+
+
+
+
+ + +
+
+

+ Have a problem or want to get in touch with others? +
+ Join our Discord! +

+ +
+
+ + + +
+
+
+
+ Simple server setup +
+
+

Server Setup.

+

 

+

+ Lörem ipsum tosk nedust hemiktigt ifall hubot, och haför. Vagt + antevynat. Antivaxxare krana: liksom monoktigt ide i ubel. + Mikroktig dovis preliga, ogt. Dihagen säs såvis nun. Dra åt + helvete +

+
+
+ +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+ +
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure? +
+
+
+ Lörem ipsum pongar valig pede, tisade. Tilåskapet decijål ifall + sar pure. Klittra tidögt och soment, viradade utivis. Sebina + hedersvåld profavis. Hurade gon vimöde. Lörem Sebina hedersvåld + profavis. Hurade gon vimöde. LöremSebina hedersvåld profavis. + Hurade gon vimöde. LöremSebina hedersvåld profavis. Hurade gon + vimöde. LöremSebina hedersvåld profavis. Hurade gon vimöde. + Lörem +
+
+
+
+
+
+ + +
+ + +
+ + + + diff --git a/services/nginx/html/map.html b/services/nginx/html/map.html index 5181c01..a6b9782 100644 --- a/services/nginx/html/map.html +++ b/services/nginx/html/map.html @@ -1,59 +1,72 @@ - - - - - - - - - - - - - - - AzureMine | Team - - - - - - -
- -
- - -
- - -
- - - - + + + + + + + + + + + + + + + Rubik Create | Team + + + + + + +
+ +
+ + +
+ + +
+ + + + diff --git a/services/nginx/html/team.html b/services/nginx/html/team.html index 359b746..356dcd2 100644 --- a/services/nginx/html/team.html +++ b/services/nginx/html/team.html @@ -1,72 +1,84 @@ - - - - - - - - - - - - - - - AzureMine | Team - - - - - - - - - -
-
-
-
- - -
- - -
- - - - + + + + + + + + + + + + + + + Rubik Create | Team + + + + + + + + + +
+
+
+ + +
+ + +
+ + + + diff --git a/services/prometheus/prometheus.yml b/services/prometheus/prometheus.yml index 88d9b39..821aede 100644 --- a/services/prometheus/prometheus.yml +++ b/services/prometheus/prometheus.yml @@ -12,6 +12,10 @@ scrape_configs: - targets: - 'minecraft:25585' + - job_name: 'minecraft_prometheus' + static_configs: + - targets: + - 'minecraft:19565' - job_name: 'minecraft_server' static_configs: