Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b1a7845c4 | |||
| 117a0501c0 | |||
| 0e2c2f072a | |||
| abe84fb542 | |||
| 9e618dbe2a |
@@ -1,66 +0,0 @@
|
||||
# Contributing to Dragonfly
|
||||
|
||||
First, thank you for your interest in contributing to Dragonfly. :tada:
|
||||
|
||||
The following is a set of guidelines for contributing to Dragonfly. These are guidelines and in
|
||||
general it is recommended to stick to them when contributing, but you should use your best
|
||||
judgement. Feel free to propose changes to this document in our Discord.
|
||||
|
||||
### Issues
|
||||
Issues are very welcome, both in the form of bug reports and feature requests.
|
||||
|
||||
When opening a bug report, please try to provide as much information as you can to reproduce the issue. When opening a
|
||||
feature request, please be as clear as you can and provide concrete examples of how you think the proposed feature
|
||||
should work. The more precise you are, the easier it is for someone to pick up your request. Don't be afraid to propose
|
||||
any ideas you might have, they may help people looking to pick up your feature request!
|
||||
|
||||
As soon as a maintainer sees your issue and has enough information, your issue will be added to a milestone that
|
||||
indicates when we hope to have your issue resolved.
|
||||
|
||||
### Pull Requests
|
||||
In general, it is recommended to discuss any changes you would like to make in our Discord to
|
||||
before making changes, unless the change-set is otherwise small or limited to a specific part of
|
||||
the code base.
|
||||
|
||||
When reviewing pull requests, we aim to reach the following goals with the code proposed:
|
||||
* Maintain the quality of the source code.
|
||||
* Stick to the standard formatting of Go (go fmt).
|
||||
* Provide a well-documented, simple and clean codebase.
|
||||
|
||||
To make sure your pull request satisfies those points, we recommend you do the following before
|
||||
opening a pull request:
|
||||
* Run `go fmt` on any files you have changed to ensure the formatting is correct. Some IDEs have
|
||||
integration with this tool to run it automatically when committing. GoLand has a box that may be
|
||||
checked in the bottom-right corner when creating a commit.
|
||||
* Make sure to provide documentation for symbols where adequate. We generally follow the following
|
||||
conventions for documentation in pull requests:
|
||||
- Exported symbols (TypeName, FunctionName) should always have documentation over them, but if
|
||||
the function exists merely to satisfy an interface, the documentation may read
|
||||
`// FunctionName ...` or be completely absent if the method has no functional behaviour.
|
||||
- Unexported symbols (typeName, functionName) _should_ have documentation, but doing so is not
|
||||
mandatory if the function is very simple and needs no clarification.
|
||||
* Make sure to use British English and proper punctuation throughout symbol names, variables and
|
||||
documentation.
|
||||
* Where possible, try to expose as few exported symbols (functions, types) as possible, unless
|
||||
strictly necessary. This makes it easier for us to change code in the future and ensures that
|
||||
users cannot use functions not suitable for the API.
|
||||
* In big functions it can become difficult to track the execution flow. Try to `return` as quickly
|
||||
possible in functions so that the main code flow is minimally indented and therefore easy to
|
||||
track and understand.
|
||||
* In places where there are three or more sequential variable declarations, these should be grouped
|
||||
into a single `var ( )` block.
|
||||
* Try to be conservative with the usage of generics. While these are often useful, they can quickly
|
||||
pollute code if used in excess. Consider carefully if generics are needed, particularly for
|
||||
exported types and functions. Don't hesitate on using them if they are able to clean up the code
|
||||
significantly.
|
||||
* We strive to have only completely functional features in the codebase. While we recognise that
|
||||
it is not always possible to provide full functionality for a feature in a single pull request,
|
||||
you should attempt to do so to the extent that you can. Specific smaller features part of the
|
||||
pull request that cannot be implemented yet should be marked with a `// TODO: ...` comment so
|
||||
that we can implement these when the required functionality is present.
|
||||
* When you open a PR, we assume you have tested your code and made sure it is working as intended.
|
||||
As a general recommendation, you should enable the Minecraft Content Log in the Profile settings
|
||||
so that it becomes obvious when invalid data is sent to the client.
|
||||
|
||||
If you run into a problem or otherwise need help with your pull request, please feel free to reach
|
||||
out to us on Discord, so we can work towards a complete pull request together.
|
||||
@@ -1,22 +0,0 @@
|
||||
name: Build
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Deploy
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, closed]
|
||||
jobs:
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
if: github.event.action != 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build
|
||||
run: go build -o dragonfly_exe -v .
|
||||
|
||||
- name: Deploy test server
|
||||
run: |
|
||||
curl -X POST https://df-mc.dev/pullrequest \
|
||||
-H "X-API-Key: ${{ secrets.API_KEY }}" \
|
||||
-F "pr=${{ github.event.pull_request.number }}" \
|
||||
-F "binary=@dragonfly_exe"
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
if: github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete test server
|
||||
run: |
|
||||
curl -X DELETE https://df-mc.dev/pullrequest/${{ github.event.pull_request.number }} \
|
||||
-H "X-API-Key: ${{ secrets.API_KEY }}"
|
||||
@@ -1,93 +0,0 @@
|
||||
name: Build and deploy
|
||||
on: [push]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'df-mc/dragonfly'
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Build
|
||||
run: go build -o dragonfly_exe -v .
|
||||
|
||||
- name: Set SSH info
|
||||
env:
|
||||
SSH_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
|
||||
SSH_PRIVATE_KEY: ${{ secrets.VPS_PRIVATE_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
|
||||
- name: Stop server
|
||||
env:
|
||||
HOST: ${{ secrets.VPS_HOST }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_rsa $HOST screen -d -R -S dragonfly -X stuff '^C'
|
||||
ssh -i ~/.ssh/id_rsa $HOST rm -f dragonfly_exe
|
||||
|
||||
- name: Transfer executable
|
||||
env:
|
||||
HOST: ${{ secrets.VPS_HOST }}
|
||||
run: |
|
||||
scp -i ~/.ssh/id_rsa dragonfly_exe $HOST:/home/dragonfly_exe
|
||||
|
||||
- name: Restart server
|
||||
env:
|
||||
HOST: ${{ secrets.VPS_HOST }}
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_rsa $HOST "screen -d -R -S dragonfly -X stuff '/home/dragonfly_exe\n'"
|
||||
|
||||
update_contributors:
|
||||
name: Update Contributors
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'df-mc/dragonfly' && github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
client-id: ${{ vars.PUSH_CLIENT_ID }}
|
||||
private-key: ${{ secrets.PUSH_SECRET }}
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
- name: Fetch Contributors
|
||||
run: |
|
||||
CONTRIBUTOR_DATA=$(curl -H "Accept: application/vnd.github+json" https://api.github.com/repos/df-mc/dragonfly/contributors?per_page=9999)
|
||||
echo $CONTRIBUTOR_DATA
|
||||
if [ $(echo $CONTRIBUTOR_DATA | jq type) == '"array"' ]; then echo -e "// Code generated by .github/workflows/push.yml; DO NOT EDIT\n\npackage session\n\n// enchantNames are names translated to the 'Standard Galactic Alphabet' client-side. The names generally have no meaning\n// on the vanilla server implementation, so we can sneak some easter eggs in here without anyone noticing.\nvar enchantNames = []string{"$(echo $CONTRIBUTOR_DATA | jq .[].login | sed -r -e "s/([^A-Z\"])([A-Z])/\1 \2/g" | sed "s/./\L&/g" | sort | sed -z "s/\n/,/g")"}" > server/session/enchantment_texts.go && gofmt -w server/session/enchantment_texts.go; fi
|
||||
- name: Push Changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "updated contributor list"
|
||||
@@ -29,6 +29,16 @@ go build -o mc_server main.go
|
||||
```
|
||||
Tekan tombol `Ctrl+C` di terminal untuk mematikan server dengan aman.
|
||||
|
||||
---
|
||||
Dan ini cara install dengan simple
|
||||
|
||||
```bash
|
||||
apt update && apt upgrade -y
|
||||
apt install git golang -y
|
||||
git clone https://nt-server.quoll-karat.ts.net/TarnaWijaya/mc.git ||
|
||||
cd mc
|
||||
go run .
|
||||
```
|
||||
---
|
||||
|
||||
## Fitur Khusus Server Ini:
|
||||
|
||||
+52
-23
@@ -21,7 +21,7 @@ type BanEntry struct {
|
||||
|
||||
// BanManager handles player bans.
|
||||
type BanManager struct {
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
bans map[string]BanEntry
|
||||
path string
|
||||
}
|
||||
@@ -54,14 +54,15 @@ func (bm *BanManager) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save saves the banned players to bans.json.
|
||||
func (bm *BanManager) Save() error {
|
||||
bm.mu.Lock()
|
||||
defer bm.mu.Unlock()
|
||||
// save writes bans to disk. Must be called WITHOUT holding bm.mu.
|
||||
func (bm *BanManager) save() error {
|
||||
bm.mu.RLock()
|
||||
var list []BanEntry
|
||||
for _, entry := range bm.bans {
|
||||
list = append(list, entry)
|
||||
}
|
||||
bm.mu.RUnlock()
|
||||
|
||||
data, err := json.MarshalIndent(list, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -69,23 +70,30 @@ func (bm *BanManager) Save() error {
|
||||
return os.WriteFile(bm.path, data, 0644)
|
||||
}
|
||||
|
||||
// Save saves the banned players to bans.json.
|
||||
func (bm *BanManager) Save() error {
|
||||
return bm.save()
|
||||
}
|
||||
|
||||
// Ban bans a player by name, with optional XUID, UUID, and IP.
|
||||
// IP bans are intentionally NOT stored to prevent blocking all
|
||||
// players on the same local network (LAN/nethernet).
|
||||
func (bm *BanManager) Ban(name string, duration time.Duration, reason string, xuid string, uuidStr string, ip string) {
|
||||
bm.mu.Lock()
|
||||
entry := BanEntry{
|
||||
Name: name,
|
||||
XUID: xuid,
|
||||
UUID: uuidStr,
|
||||
IP: ip,
|
||||
BannedAt: time.Now(),
|
||||
Reason: reason,
|
||||
Name: name,
|
||||
XUID: xuid,
|
||||
UUID: uuidStr,
|
||||
IP: "", // Do NOT store IP to prevent LAN-wide bans
|
||||
BannedAt: time.Now(),
|
||||
Reason: reason,
|
||||
}
|
||||
if duration > 0 {
|
||||
entry.ExpiresAt = time.Now().Add(duration)
|
||||
}
|
||||
bm.bans[strings.ToLower(name)] = entry
|
||||
bm.mu.Unlock()
|
||||
_ = bm.Save()
|
||||
_ = bm.save()
|
||||
}
|
||||
|
||||
// Unban unbans a player by name.
|
||||
@@ -98,15 +106,28 @@ func (bm *BanManager) Unban(name string) bool {
|
||||
}
|
||||
bm.mu.Unlock()
|
||||
if exists {
|
||||
_ = bm.Save()
|
||||
_ = bm.save()
|
||||
}
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsBanned checks if a player is banned by name, XUID, UUID, or IP.
|
||||
func (bm *BanManager) IsBanned(name, xuid, uuidStr, ip string) (BanEntry, bool) {
|
||||
// UnbanAll removes all bans. Useful for emergency reset.
|
||||
func (bm *BanManager) UnbanAll() int {
|
||||
bm.mu.Lock()
|
||||
defer bm.mu.Unlock()
|
||||
count := len(bm.bans)
|
||||
bm.bans = make(map[string]BanEntry)
|
||||
bm.mu.Unlock()
|
||||
if count > 0 {
|
||||
_ = bm.save()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// IsBanned checks if a player is banned by name, XUID, or UUID.
|
||||
// IP-based checking is intentionally excluded to prevent false
|
||||
// positives on local networks where all players share the same IP.
|
||||
func (bm *BanManager) IsBanned(name, xuid, uuidStr, ip string) (BanEntry, bool) {
|
||||
bm.mu.RLock()
|
||||
|
||||
now := time.Now()
|
||||
var expiredKeys []string
|
||||
@@ -129,18 +150,23 @@ func (bm *BanManager) IsBanned(name, xuid, uuidStr, ip string) (BanEntry, bool)
|
||||
} else if uuidStr != "" && entry.UUID != "" && strings.EqualFold(entry.UUID, uuidStr) {
|
||||
foundEntry = entry
|
||||
found = true
|
||||
} else if ip != "" && entry.IP != "" && entry.IP == ip {
|
||||
foundEntry = entry
|
||||
found = true
|
||||
}
|
||||
// NOTE: IP matching is intentionally removed.
|
||||
// On local networks (LAN/nethernet), all players share the
|
||||
// same IP address. Matching by IP would block ALL players
|
||||
// from the same network when only one should be banned.
|
||||
}
|
||||
}
|
||||
bm.mu.RUnlock()
|
||||
|
||||
// Clean up expired entries with a separate write lock.
|
||||
if len(expiredKeys) > 0 {
|
||||
bm.mu.Lock()
|
||||
for _, k := range expiredKeys {
|
||||
delete(bm.bans, k)
|
||||
}
|
||||
go func() { _ = bm.Save() }()
|
||||
bm.mu.Unlock()
|
||||
go func() { _ = bm.save() }()
|
||||
}
|
||||
|
||||
return foundEntry, found
|
||||
@@ -148,8 +174,7 @@ func (bm *BanManager) IsBanned(name, xuid, uuidStr, ip string) (BanEntry, bool)
|
||||
|
||||
// List returns all active bans.
|
||||
func (bm *BanManager) List() []BanEntry {
|
||||
bm.mu.Lock()
|
||||
defer bm.mu.Unlock()
|
||||
bm.mu.RLock()
|
||||
var active []BanEntry
|
||||
now := time.Now()
|
||||
var expiredKeys []string
|
||||
@@ -160,11 +185,15 @@ func (bm *BanManager) List() []BanEntry {
|
||||
}
|
||||
active = append(active, entry)
|
||||
}
|
||||
bm.mu.RUnlock()
|
||||
|
||||
if len(expiredKeys) > 0 {
|
||||
bm.mu.Lock()
|
||||
for _, k := range expiredKeys {
|
||||
delete(bm.bans, k)
|
||||
}
|
||||
go func() { _ = bm.Save() }()
|
||||
bm.mu.Unlock()
|
||||
go func() { _ = bm.save() }()
|
||||
}
|
||||
return active
|
||||
}
|
||||
|
||||
@@ -533,6 +533,15 @@ func (p *Player) handleCheatCommand(name string, args []string) bool {
|
||||
}
|
||||
return true
|
||||
|
||||
case "unbanall":
|
||||
count := Bans.UnbanAll()
|
||||
if count > 0 {
|
||||
p.Message(fmt.Sprintf("Successfully unbanned all %d players.", count))
|
||||
} else {
|
||||
p.Message("No players were banned.")
|
||||
}
|
||||
return true
|
||||
|
||||
case "gamemode", "gm":
|
||||
if len(args) < 1 {
|
||||
p.Message("Usage: /gamemode <survival/creative/adventure/spectator/0/1/2/3> [player]")
|
||||
|
||||
+47
-8
@@ -349,33 +349,64 @@ func (srv *Server) listen(l Listener) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
srv.conf.Log.Error(fmt.Sprintf("panic handling connection: %v", r))
|
||||
_ = c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
name := c.IdentityData().DisplayName
|
||||
xuid := c.IdentityData().XUID
|
||||
uuidStr := c.IdentityData().Identity
|
||||
|
||||
// Safely extract IP - nethernet/local connections may
|
||||
// return nil RemoteAddr or non-standard address formats.
|
||||
ip := ""
|
||||
if addr := c.RemoteAddr(); addr != nil {
|
||||
if host, _, err := net.SplitHostPort(addr.String()); err == nil {
|
||||
ip = host
|
||||
} else {
|
||||
ip = addr.String()
|
||||
addrStr := addr.String()
|
||||
if addrStr != "" {
|
||||
if host, _, err := net.SplitHostPort(addrStr); err == nil {
|
||||
ip = host
|
||||
} else {
|
||||
ip = addrStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
srv.conf.Log.Debug("Player connecting...", "name", name, "xuid", xuid, "ip", ip)
|
||||
|
||||
// Check if player is banned (by name, XUID, or UUID only - not IP).
|
||||
if entry, banned := player.Bans.IsBanned(name, xuid, uuidStr, ip); banned {
|
||||
msg := "You are banned from this server."
|
||||
if entry.Reason != "" {
|
||||
msg += " Reason: " + entry.Reason
|
||||
}
|
||||
if !entry.ExpiresAt.IsZero() {
|
||||
timeLeft := time.Until(entry.ExpiresAt).Round(time.Second)
|
||||
msg = fmt.Sprintf("You are banned from this server. Remaining time: %v", timeLeft)
|
||||
if timeLeft > 0 {
|
||||
msg += fmt.Sprintf(" Remaining: %v", timeLeft)
|
||||
}
|
||||
}
|
||||
srv.conf.Log.Debug("Player banned, rejecting.", "name", name)
|
||||
_ = c.WritePacket(&packet.Disconnect{Message: msg})
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
if srv.PlayerCount() >= 14 && !strings.EqualFold(name, "XTarnaWijaya") && !strings.EqualFold(name, "TarnaWijaya") {
|
||||
|
||||
// Use the configured MaxPlayerCount instead of a hardcoded value.
|
||||
// Admins bypass the player limit.
|
||||
if srv.PlayerCount() >= srv.MaxPlayerCount() &&
|
||||
!strings.EqualFold(name, "XTarnaWijaya") &&
|
||||
!strings.EqualFold(name, "TarnaWijaya") {
|
||||
srv.conf.Log.Debug("Server full, rejecting.", "name", name)
|
||||
_ = c.WritePacket(&packet.Disconnect{Message: "Server is full."})
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if msg, ok := srv.conf.Allower.Allow(c.RemoteAddr(), c.IdentityData(), c.ClientData()); !ok {
|
||||
srv.conf.Log.Debug("Player not allowed.", "name", name, "msg", msg)
|
||||
_ = c.WritePacket(&packet.Disconnect{HideDisconnectionScreen: msg == "", Message: msg})
|
||||
_ = c.Close()
|
||||
return
|
||||
@@ -469,12 +500,20 @@ func (srv *Server) finaliseConn(ctx context.Context, conn session.Conn, l Listen
|
||||
if err := conn.StartGameContext(ctx, data); err != nil {
|
||||
_ = l.Disconnect(conn, "Connection timeout.")
|
||||
|
||||
srv.conf.Log.Debug("spawn failed: "+err.Error(), "raddr", conn.RemoteAddr())
|
||||
raddr := "nethernet"
|
||||
if addr := conn.RemoteAddr(); addr != nil {
|
||||
raddr = addr.String()
|
||||
}
|
||||
srv.conf.Log.Debug("spawn failed: "+err.Error(), "raddr", raddr)
|
||||
return
|
||||
}
|
||||
if _, ok := srv.Player(id); ok {
|
||||
_ = l.Disconnect(conn, "Already logged in.")
|
||||
srv.conf.Log.Debug("spawn failed: already logged in", "raddr", conn.RemoteAddr())
|
||||
raddr := "nethernet"
|
||||
if addr := conn.RemoteAddr(); addr != nil {
|
||||
raddr = addr.String()
|
||||
}
|
||||
srv.conf.Log.Debug("spawn failed: already logged in", "raddr", raddr)
|
||||
return
|
||||
}
|
||||
_ = conn.WritePacket(&packet.ItemRegistry{Items: srv.customItems})
|
||||
|
||||
@@ -176,7 +176,11 @@ func (conf Config) New(conn Conn) *Session {
|
||||
if conf.Log == nil {
|
||||
conf.Log = slog.Default()
|
||||
}
|
||||
conf.Log = conf.Log.With("name", conn.IdentityData().DisplayName, "uuid", conn.IdentityData().Identity, "raddr", conn.RemoteAddr().String())
|
||||
raddr := "nethernet"
|
||||
if addr := conn.RemoteAddr(); addr != nil {
|
||||
raddr = addr.String()
|
||||
}
|
||||
conf.Log = conf.Log.With("name", conn.IdentityData().DisplayName, "uuid", conn.IdentityData().Identity, "raddr", raddr)
|
||||
|
||||
s := &Session{}
|
||||
*s = Session{
|
||||
|
||||
Reference in New Issue
Block a user