diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5c1784 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +diffs +build_logs \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..e807568 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,145 @@ +# build 1 +labels: + hostname: "macmini7" +clone: + git: + image: woodpeckerci/plugin-git + settings: + partial: false + depth: 1 +steps: + # Build Step for staging Branch + build-staging: + name: build-staging + image: woodpeckerci/plugin-docker-buildx + secrets: [REGISTRY_USER, REGISTRY_PASSWORD] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "Building application for staging branch" + - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us + - echo compose build + - docker compose -f docker-compose.staging.yml build --no-cache + when: + branch: main + event: push + # path: + # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] + + deploy-new: + name: deploy-new + when: + branch: main + # path: + # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] + image: woodpeckerci/plugin-docker-buildx + secrets: [REGISTRY_USER, REGISTRY_PASSWORD] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us + - echo compose push + - docker compose -f docker-compose.staging.yml push + # - docker stack deploy --with-registry-auth -c ./stack.staging.yml $${CI_REPO_NAME}-staging + + # # Wait for Deploy to Complete + # wait-for-deploy-staging: + # name: wait-for-deploy-staging + # image: woodpeckerci/plugin-git + # commands: + # - echo "Waiting for staging deploy step to complete rollout." + # - sleep 60 + # when: + # - branch: main + # - event: push + + # # Run Automated Tests on staging Branch + # test-staging: + # name: run-tests-staging + # image: git.nixc.us/colin/playwright:latest + # secrets: [ base_url ] + # when: + # - branch: main + # - event: push + # - path: + # include: [ 'tests/', 'src/','docker-compose.staging.yml', 'docker-compose.production.yml', '*.tests.ts' ] # Specify paths relevant to tests + # volumes: + # - /var/run/docker.sock:/var/run/docker.sock:ro + + cleanup-staging: + name: cleanup-staging + when: + branch: main + # path: + # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] + image: woodpeckerci/plugin-docker-buildx + secrets: [REGISTRY_USER, REGISTRY_PASSWORD] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + # - docker stack rm $${CI_REPO_NAME}-staging + ## added fault tolerance for docker stack rm + - for i in {1..5}; do docker stack rm ${CI_REPO_NAME}-staging && break || sleep 10; done + - docker compose -f docker-compose.staging.yml down + - docker compose -f docker-compose.staging.yml rm -f + + # Build Step for staging Branch + build-push-production: + name: build-push-production + image: woodpeckerci/plugin-docker-buildx + secrets: [REGISTRY_USER, REGISTRY_PASSWORD] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "Building application for staging branch" + - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us + - echo compose build + - docker compose -f docker-compose.production.yml build --no-cache + - docker compose -f docker-compose.production.yml push + when: + branch: main + event: [push, cron] + # path: + # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] + + # Deploy to Production Branch + deploy-production: + name: deploy-production + image: woodpeckerci/plugin-docker-buildx + secrets: [REGISTRY_USER, REGISTRY_PASSWORD] + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us + - docker stack deploy --with-registry-auth -c ./stack.production.yml $${CI_REPO_NAME} + # - docker image rm git.nixc.us/colin/$${CI_REPO_NAME}:production + when: + branch: main + event: [push, cron] + # path: + # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] + + + # # Wait for Deploy to Complete + # wait-for-deploy-production: + # name: wait-for-deploy-production + # image: woodpeckerci/plugin-git + # commands: + # - echo "Waiting for deploy step to complete rollout." + # - sleep 60 + # when: + # branch: main + # event: push + + # # Run Post-Deployment Smoke Tests + # post-deploy-smoke-tests-git-nixc-us: + # name: run-post-deploy-smoke-tests-git-nixc-us + # image: git.nixc.us/colin/playwright:latest + # # secrets: [TEST_USER, TEST_PASSWORD] + # environment: + # - BASE_URL=https://git.nixc.us + # when: + # branch: main + # event: push + # # path: + # # include: [ 'stack.production.yml', 'stack.staging.yml', 'docker-compose.staging.yml', 'docker-compose.production.yml', 'Dockerfile', '*.tests.ts' ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1985799 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ + +# Oculus Toolchain + +Oculus is a toolchain for monitoring and handling Docker container diffs. It includes three main components: +- **filter**: Filters the diff outputs based on ignore patterns. +- **api**: Provides an API to fetch container details and diffs. +- **main**: The main monitoring application that uses the API and filter components to manage container diffs. + +## Installation + +To install the Oculus toolchain, you can use the provided `install.sh` script. This script will download and install the appropriate binaries for your system. + +### Prerequisites + +- **curl**: Make sure `curl` is installed on your system. +- **sudo**: Required if you do not have write permission to `/usr/local/bin`. + +### Installation Steps + +1. **Download the Install Script**: + ```sh + curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh -o install.sh + ``` + +2. **Make the Script Executable**: + ```sh + chmod +x install.sh + ``` + +3. **Run the Install Script**: + ```sh + ./install.sh + ``` + +This will download and install the following components to `/usr/local/bin`: +- `oculus_filter` +- `oculus_api` +- `oculus_main` + +## Usage + +After installation, you can start using the Oculus toolchain. Below are the basic usage instructions for each component: + +### Starting the API Server + +To start the API server, run: +```sh +oculus_api +``` +This will start the API server on port 8080. + +### Running the Main Monitoring Application + +Before running the main monitoring application, ensure that the API server is running and the environment variables are set: + +```sh +export API_ADDRESS="http://localhost:8080" +export GLITCHTIP_DSN="your-glitchtip-dsn" + +oculus_main +``` + +### Filtering Diff Outputs + +To manually filter a diff output file, you can use the `filter` component: + +```sh +oculus_filter /path/to/diff/file "ignore_pattern1" "ignore_pattern2" +``` + +## Docker Integration + +You can integrate Oculus into your Docker setup by including the installation script in your Dockerfile. Below is an example Dockerfile: + +```dockerfile +FROM ubuntu:latest + +# Install dependencies +RUN apt-get update && apt-get install -y curl + +# Download and run the Oculus install script +RUN curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh -o install.sh && \ + chmod +x install.sh && \ + ./install.sh + +# Set environment variables +ENV API_ADDRESS="http://localhost:8080" +ENV GLITCHTIP_DSN="your-glitchtip-dsn" + +# Start the API server and the main application +CMD ["sh", "-c", "oculus_api & oculus_main"] +``` + +### Building the Docker Image + +To build the Docker image, run: + +```sh +docker build -t oculus-toolchain . +``` + +### Running the Docker Container + +To run the Docker container, use: + +```sh +docker run -d --name oculus-toolchain oculus-toolchain +``` \ No newline at end of file diff --git a/api.go b/api.go new file mode 100644 index 0000000..4b2de5b --- /dev/null +++ b/api.go @@ -0,0 +1,115 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os/exec" + "strings" +) + +type ContainerInfo struct { + ID string + Name string + CName string + Interval string + Ignores []string +} + +func listContainersHandler(w http.ResponseWriter, r *http.Request) { + containers, err := listContainers() + if err != nil { + http.Error(w, fmt.Sprintf("Error listing containers: %v", err), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(containers); err != nil { + http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError) + } +} + +func getDiffHandler(w http.ResponseWriter, r *http.Request) { + containerID := r.URL.Query().Get("id") + if containerID == "" { + http.Error(w, "Missing container ID", http.StatusBadRequest) + return + } + diffOutput, err := getDiffOutput(containerID) + if err != nil { + http.Error(w, fmt.Sprintf("Error getting diff for container %s: %v", containerID, err), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(diffOutput)) +} + +func startServer() { + http.HandleFunc("/containers", listContainersHandler) + http.HandleFunc("/diff", getDiffHandler) + + log.Println("Starting server on :8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatalf("Error starting server: %v", err) + } +} + +func listContainers() ([]ContainerInfo, error) { + output, err := exec.Command("docker", "ps", "--format", "{{.ID}} {{.Names}} {{.Label \"oculus.enable\"}} {{.Label \"oculus.interval\"}} {{.Label \"oculus.cname\"}} {{.Label \"oculus.ignores\"}}").Output() + if err != nil { + return nil, err + } + + lines := strings.Split(string(output), "\n") + var containers []ContainerInfo + + for _, line := range lines { + if line == "" { + continue + } + parts := strings.Fields(line) + if len(parts) < 3 || parts[2] == "" { + continue + } + + id := parts[0] + name := parts[1] + interval := "300s" + if len(parts) > 3 && parts[3] != "" { + interval = parts[3] + } + + cname := name + if len(parts) > 4 && parts[4] != "" { + cname = parts[4] + } + + ignores := []string{} + if len(parts) > 5 && parts[5] != "" { + ignores = strings.Split(parts[5], ",") + } + + containers = append(containers, ContainerInfo{ + ID: id, + Name: name, + CName: cname, + Interval: interval, + Ignores: ignores, + }) + } + + return containers, nil +} + +func getDiffOutput(containerID string) (string, error) { + cmd := exec.Command("docker", "diff", containerID) + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("docker diff failed: %w", err) + } + return string(output), nil +} + +func main() { + startServer() +} diff --git a/build.sh b/build.sh index d19659e..ca24c8c 100755 --- a/build.sh +++ b/build.sh @@ -1,70 +1,75 @@ #!/bin/bash -# Default architecture -DEFAULT_ARCH="linux/amd64" +ARCHITECTURES=("darwin/arm64" "linux/amd64" "linux/arm64" "darwin/amd64" "windows/amd64") +PROJECT_NAME=$(basename "$(pwd)") -# Supported architectures (adjust as needed) -ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64") - -# Ensure all necessary directories exist and go modules are ready prepare_build() { - # Create necessary directories if they don't exist - mkdir -p dist - mkdir -p build_logs - - # Initialize go modules if go.mod does not exist + mkdir -p dist build_logs if [ ! -f go.mod ]; then - echo "Initializing Go modules" - go mod init yourmodule # Replace 'yourmodule' with your actual module name or path + go mod init "$PROJECT_NAME" fi - - # Fetch and ensure all dependencies are up to date - echo "Checking dependencies..." go mod tidy } -# Build function build_binary() { - os=$1 - arch=$2 - output_name="oculus" + local os=$1 + local arch=$2 + local output_main="dist/${PROJECT_NAME}_${os}_${arch}_main" + local output_filter="dist/${PROJECT_NAME}_${os}_${arch}_filter" + local output_api="dist/${PROJECT_NAME}_${os}_${arch}_api" - if [[ "$os/$arch" != "$DEFAULT_ARCH" ]]; then - output_name="${output_name}_${os}_${arch}" - fi - - output_name="dist/${output_name}" - - # Dynamic Linking - echo "Building dynamically linked for ${os}/${arch} -> ${output_name}" - GOOS=${os} GOARCH=${arch} go build -o ${output_name} main.go 2>build_logs/${os}_${arch}_build.log - if [ $? -eq 0 ]; then - echo "Successfully built ${output_name}" + env GOOS=$os GOARCH=$arch go build -o $output_main main.go &> "build_logs/${os}_${arch}_main.log" + if [ $? -ne 0 ]; then + echo "Build failed for $os/$arch main" >> "build_logs/error.log" else - echo "Failed to build ${output_name}. Check build_logs/${os}_${arch}_build.log for errors." + echo "Build succeeded for $os/$arch main" fi - # Static Linking - if [[ "$os" == "linux" ]]; then # Typically, static linking is most relevant for Linux environments - static_output_name="${output_name}_static" - echo "Building statically linked for ${os}/${arch} -> ${static_output_name}" - CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go build -a -ldflags '-extldflags "-static"' -o ${static_output_name} main.go 2>build_logs/${os}_${arch}_static_build.log - if [ $? -eq 0 ]; then - echo "Successfully built ${static_output_name}" + env GOOS=$os GOARCH=$arch go build -o $output_filter filter.go &> "build_logs/${os}_${arch}_filter.log" + if [ $? -ne 0 ]; then + echo "Build failed for $os/$arch filter" >> "build_logs/error.log" + else + echo "Build succeeded for $os/$arch filter" + fi + + env GOOS=$os GOARCH=$arch go build -o $output_api api.go &> "build_logs/${os}_${arch}_api.log" + if [ $? -ne 0 ]; then + echo "Build failed for $os/$arch api" >> "build_logs/error.log" + else + echo "Build succeeded for $os/$arch api" + fi + + if [ "$os" == "linux" ]; then + env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_main}_static" main.go &> "build_logs/${os}_${arch}_main_static.log" + if [ $? -ne 0 ]; then + echo "Static build failed for $os/$arch main" >> "build_logs/error.log" else - echo "Failed to build ${static_output_name}. Check build_logs/${os}_${arch}_static_build.log for errors." + echo "Static build succeeded for $os/$arch main" + fi + + env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_filter}_static" filter.go &> "build_logs/${os}_${arch}_filter_static.log" + if [ $? -ne 0 ]; then + echo "Static build failed for $os/$arch filter" >> "build_logs/error.log" + else + echo "Static build succeeded for $os/$arch filter" + fi + + env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output_api}_static" api.go &> "build_logs/${os}_${arch}_api_static.log" + if [ $? -ne 0 ]; then + echo "Static build failed for $os/$arch api" >> "build_logs/error.log" + else + echo "Static build succeeded for $os/$arch api" fi fi } -# Main Build Process -prepare_build -for arch in "${ARCHITECTURES[@]}"; do - IFS='/' read -r -a parts <<< "$arch" # Split architecture string - os=${parts[0]} - arch=${parts[1]} - build_binary $os $arch -done - -echo "Build process completed." +main() { + prepare_build + for arch in "${ARCHITECTURES[@]}"; do + IFS="/" read -r os arch <<< "$arch" + build_binary $os $arch + done + echo "Build process completed." +} +main diff --git a/build_logs/darwin_amd64_build.log b/build_logs/darwin_amd64_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/darwin_amd64_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/darwin_arm64_build.log b/build_logs/darwin_arm64_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/darwin_arm64_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_amd64_build.log b/build_logs/linux_amd64_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_amd64_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_amd64_static_build.log b/build_logs/linux_amd64_static_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_amd64_static_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_arm64_build.log b/build_logs/linux_arm64_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_arm64_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_arm64_static_build.log b/build_logs/linux_arm64_static_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_arm64_static_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_arm_build.log b/build_logs/linux_arm_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_arm_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/build_logs/linux_arm_static_build.log b/build_logs/linux_arm_static_build.log deleted file mode 100644 index 8f7ba50..0000000 --- a/build_logs/linux_arm_static_build.log +++ /dev/null @@ -1,6 +0,0 @@ -# command-line-arguments -./main.go:9:5: "os" imported and not used -./main.go:11:5: "strings" imported and not used -./main.go:12:5: "time" imported and not used -./main.go:15:5: "github.com/docker/docker/api/types/events" imported and not used -./main.go:34:70: undefined: types.ContainerListOptions diff --git a/dist/oculus_darwin_amd64_api b/dist/oculus_darwin_amd64_api new file mode 100755 index 0000000..9c8a042 Binary files /dev/null and b/dist/oculus_darwin_amd64_api differ diff --git a/dist/oculus_darwin_amd64_filter b/dist/oculus_darwin_amd64_filter new file mode 100755 index 0000000..ecba7ad Binary files /dev/null and b/dist/oculus_darwin_amd64_filter differ diff --git a/dist/oculus_darwin_amd64_main b/dist/oculus_darwin_amd64_main new file mode 100755 index 0000000..c38b89c Binary files /dev/null and b/dist/oculus_darwin_amd64_main differ diff --git a/dist/oculus_darwin_arm64_api b/dist/oculus_darwin_arm64_api new file mode 100755 index 0000000..5eecce0 Binary files /dev/null and b/dist/oculus_darwin_arm64_api differ diff --git a/dist/oculus_darwin_arm64_filter b/dist/oculus_darwin_arm64_filter new file mode 100755 index 0000000..b3bd419 Binary files /dev/null and b/dist/oculus_darwin_arm64_filter differ diff --git a/dist/oculus_darwin_arm64_main b/dist/oculus_darwin_arm64_main new file mode 100755 index 0000000..8935d37 Binary files /dev/null and b/dist/oculus_darwin_arm64_main differ diff --git a/dist/oculus_linux_amd64_api b/dist/oculus_linux_amd64_api new file mode 100755 index 0000000..47125f4 Binary files /dev/null and b/dist/oculus_linux_amd64_api differ diff --git a/dist/oculus_linux_amd64_api_static b/dist/oculus_linux_amd64_api_static new file mode 100755 index 0000000..feb3b1b Binary files /dev/null and b/dist/oculus_linux_amd64_api_static differ diff --git a/dist/oculus_linux_amd64_filter b/dist/oculus_linux_amd64_filter new file mode 100755 index 0000000..ab4814e Binary files /dev/null and b/dist/oculus_linux_amd64_filter differ diff --git a/dist/oculus_linux_amd64_filter_static b/dist/oculus_linux_amd64_filter_static new file mode 100755 index 0000000..d0c61e0 Binary files /dev/null and b/dist/oculus_linux_amd64_filter_static differ diff --git a/dist/oculus_linux_amd64_main b/dist/oculus_linux_amd64_main new file mode 100755 index 0000000..5104047 Binary files /dev/null and b/dist/oculus_linux_amd64_main differ diff --git a/dist/oculus_linux_amd64_main_static b/dist/oculus_linux_amd64_main_static new file mode 100755 index 0000000..a9198e3 Binary files /dev/null and b/dist/oculus_linux_amd64_main_static differ diff --git a/dist/oculus_linux_arm64_api b/dist/oculus_linux_arm64_api new file mode 100755 index 0000000..3c4d5e6 Binary files /dev/null and b/dist/oculus_linux_arm64_api differ diff --git a/dist/oculus_linux_arm64_api_static b/dist/oculus_linux_arm64_api_static new file mode 100755 index 0000000..cb10f7b Binary files /dev/null and b/dist/oculus_linux_arm64_api_static differ diff --git a/dist/oculus_linux_arm64_filter b/dist/oculus_linux_arm64_filter new file mode 100755 index 0000000..22eed99 Binary files /dev/null and b/dist/oculus_linux_arm64_filter differ diff --git a/dist/oculus_linux_arm64_filter_static b/dist/oculus_linux_arm64_filter_static new file mode 100755 index 0000000..9a36217 Binary files /dev/null and b/dist/oculus_linux_arm64_filter_static differ diff --git a/dist/oculus_linux_arm64_main b/dist/oculus_linux_arm64_main new file mode 100755 index 0000000..ebd8cc1 Binary files /dev/null and b/dist/oculus_linux_arm64_main differ diff --git a/dist/oculus_linux_arm64_main_static b/dist/oculus_linux_arm64_main_static new file mode 100755 index 0000000..4c16faf Binary files /dev/null and b/dist/oculus_linux_arm64_main_static differ diff --git a/dist/oculus_windows_amd64_api b/dist/oculus_windows_amd64_api new file mode 100755 index 0000000..0754feb Binary files /dev/null and b/dist/oculus_windows_amd64_api differ diff --git a/dist/oculus_windows_amd64_filter b/dist/oculus_windows_amd64_filter new file mode 100755 index 0000000..ff72919 Binary files /dev/null and b/dist/oculus_windows_amd64_filter differ diff --git a/dist/oculus_windows_amd64_main b/dist/oculus_windows_amd64_main new file mode 100755 index 0000000..4b631dd Binary files /dev/null and b/dist/oculus_windows_amd64_main differ diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 0000000..fe232e1 --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,6 @@ +services: + oculus: + build: + context: ./docker/oculus/ + dockerfile: Dockerfile.production + image: git.nixc.us/colin/oculus:production \ No newline at end of file diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 0000000..b98cdda --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,6 @@ +services: + oculus: + build: + context: ./docker/oculus/ + dockerfile: Dockerfile + image: git.nixc.us/colin/oculus:staging diff --git a/docker/oculus/Dockerfile b/docker/oculus/Dockerfile new file mode 100644 index 0000000..0799552 --- /dev/null +++ b/docker/oculus/Dockerfile @@ -0,0 +1,9 @@ +FROM alpine:latest +RUN apk update && apk add --no-cache curl bash +RUN curl -sSL https://git.nixc.us/colin/Oculus/raw/branch/main/install.sh | bash +RUN curl -sSL https://git.nixc.us/Nixius/go-glitch/raw/branch/master/install.sh | bash +COPY notify.sh /notify.sh +RUN chmod +x /notify.sh +ENV GLITCHTIP_DSN="" +CMD ["/usr/local/bin/oculus"] + diff --git a/docker/oculus/Dockerfile.production b/docker/oculus/Dockerfile.production new file mode 100644 index 0000000..65f2ece --- /dev/null +++ b/docker/oculus/Dockerfile.production @@ -0,0 +1 @@ +FROM git.nixc.us/colin/oculus:staging diff --git a/docker/oculus/notify.sh b/docker/oculus/notify.sh new file mode 100644 index 0000000..e69de29 diff --git a/filter.go b/filter.go new file mode 100644 index 0000000..df3e3de --- /dev/null +++ b/filter.go @@ -0,0 +1,105 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "regexp" + "strings" +) + +func main() { + if len(os.Args) < 3 { + log.Fatalf("Usage: %s [ ... ]\n", os.Args[0]) + } + + filename := os.Args[1] + patterns := os.Args[2:] + + // Compile patterns into regular expressions + regexPatterns := compilePatterns(patterns) + + // Read the file + file, err := os.Open(filename) + if err != nil { + log.Fatalf("Error opening file: %s\n", err) + } + defer file.Close() + + var filteredLines []string + var removedCount int + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + fmt.Printf("Processing line: %s\n", line) // Debug print + if matchesAnyPattern(line, regexPatterns) { + fmt.Printf("Line matches pattern, removing: %s\n", line) // Debug print + removedCount++ + } else { + filteredLines = append(filteredLines, line) + } + } + + if err := scanner.Err(); err != nil { + log.Fatalf("Error reading file: %s\n", err) + } + + // Write the filtered lines back to the file + tempFile, err := os.CreateTemp("", "filtered_output") + if err != nil { + log.Fatalf("Error creating temporary file: %s\n", err) + } + defer os.Remove(tempFile.Name()) + + for _, line := range filteredLines { + _, err = tempFile.WriteString(line + "\n") + if err != nil { + log.Fatalf("Error writing to temporary file: %s\n", err) + } + } + + tempFile.Close() + + err = os.Rename(tempFile.Name(), filename) + if err != nil { + log.Fatalf("Error renaming temporary file: %s\n", err) + } + + fmt.Printf("Filtered %d lines from %s\n", removedCount, filename) +} + +func compilePatterns(patterns []string) []*regexp.Regexp { + var regexPatterns []*regexp.Regexp + for _, pattern := range patterns { + // Convert wildcard pattern to regex pattern + regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), "\\*", ".*") + regexPattern = strings.TrimSuffix(regexPattern, `\.`) // Remove trailing dot if present + fmt.Printf("Compiled pattern: %s to regex: %s\n", pattern, regexPattern) // Debug print + re, err := regexp.Compile(regexPattern) + if err != nil { + log.Fatalf("Error compiling regex pattern: %s\n", err) + } + regexPatterns = append(regexPatterns, re) + } + return regexPatterns +} + +func matchesAnyPattern(line string, patterns []*regexp.Regexp) bool { + // Get the full path including directory + parts := strings.Fields(line) + if len(parts) < 2 { + return false + } + fullPath := parts[1] + fmt.Printf("Checking filename: %s against patterns\n", fullPath) // Debug print + + for _, pattern := range patterns { + if pattern.MatchString(fullPath) { + fmt.Printf("Pattern matched: %s in filename: %s\n", pattern.String(), fullPath) // Debug print + return true + } + } + return false +} diff --git a/go.mod b/go.mod index af7169b..4d4a0cc 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,3 @@ -module yourmodule +module oculus go 1.21.1 - -require ( - github.com/docker/docker v26.1.4+incompatible - github.com/robfig/cron/v3 v3.0.1 -) - -require ( - github.com/Microsoft/go-winio v0.4.14 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/time v0.5.0 // indirect - gotest.tools/v3 v3.5.1 // indirect -) diff --git a/go.sum b/go.sum index fc90a2f..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,123 +0,0 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU= -github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/install.sh b/install.sh index eabd34f..17c6d4b 100644 --- a/install.sh +++ b/install.sh @@ -1,15 +1,11 @@ #!/bin/bash INSTALL_DIR="/usr/local/bin" -BINARY_NAME="oculus" -BASE_URL="https://git.nixc.us/colin/oculus/raw/branch/main/dist" +PROJECT_NAME=$(basename "$(pwd)") +BASE_URL="https://git.nixc.us/colin/Oculus/raw/branch/main/dist" -declare -A binaries -binaries["linux/amd64"]="oculus_linux_amd64" -binaries["linux/arm64"]="oculus_linux_arm64" -binaries["linux/arm/v7"]="oculus_linux_arm" -binaries["darwin/amd64"]="oculus_darwin_amd64" -binaries["darwin/arm64"]="oculus_darwin_arm64" +# Supported architectures +ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64") OS="$(uname -s | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m)" @@ -21,17 +17,21 @@ case $ARCH in *) echo "Unsupported architecture: $ARCH"; exit 1 ;; esac -KEY="${OS}/${ARCH}" +COMPONENTS=("filter" "api" "main") -if [[ -z "${binaries[$KEY]}" ]]; then - echo "No pre-built binary for your system architecture ($KEY)." - exit 1 -fi +for COMPONENT in "${COMPONENTS[@]}"; do + BINARY_NAME="${PROJECT_NAME}_${COMPONENT}" + BINARY_URL="${BASE_URL}/${PROJECT_NAME}_${OS}_${ARCH}_${COMPONENT}" + echo "Downloading and installing $BINARY_NAME from $BINARY_URL..." -BINARY_URL="${BASE_URL}/${binaries[$KEY]}" + # Check if we have write permission to the install directory + if [ -w "${INSTALL_DIR}" ]; then + curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}" + chmod +x "${INSTALL_DIR}/${BINARY_NAME}" + else + sudo curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}" + sudo chmod +x "${INSTALL_DIR}/${BINARY_NAME}" + fi -echo "Downloading and installing $BINARY_NAME from $BINARY_URL..." -sudo curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}" - -sudo chmod +x "${INSTALL_DIR}/${BINARY_NAME}" -echo "Installed $BINARY_NAME to $INSTALL_DIR" + echo "Installed $BINARY_NAME to $INSTALL_DIR" +done diff --git a/main.go b/main.go index c767fd1..826fcc8 100644 --- a/main.go +++ b/main.go @@ -1,182 +1,255 @@ package main import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "strings" - "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/client" - "github.com/robfig/cron/v3" + "crypto/md5" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" ) -const ( - notifyScript = "/notify.sh" +type ContainerInfo struct { + ID string + Name string + CName string + Interval string + Ignores []string +} + +type MonitoredContainer struct { + Info ContainerInfo + LastChecked time.Time +} + +var ( + apiAddress string + glitchtipDSN string + notifiedChanges = make(map[string]string) + monitoredContainers = make(map[string]*MonitoredContainer) + mu sync.Mutex ) -// ContainerDiff represents the state of a container -type ContainerDiff struct { - ID string - Image string - Labels map[string]string -} +func init() { + apiAddress = os.Getenv("API_ADDRESS") + if apiAddress == "" { + apiAddress = "http://localhost:8080" + } -// getRunningContainers fetches the list of running containers with relevant labels -func getRunningContainers(cli *client.Client) ([]ContainerDiff, error) { - containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) - if err != nil { - return nil, err - } - - var diffs []ContainerDiff - for _, container := range containers { - if _, ok := container.Labels["oculus.containerid"]; ok { - diffs = append(diffs, ContainerDiff{ - ID: container.ID, - Image: container.Image, - Labels: container.Labels, - }) - } - } - - return diffs, nil -} - -// saveDiffs writes the container diffs to a file -func saveDiffs(diffs []ContainerDiff, filePath string) error { - data, err := json.Marshal(diffs) - if err != nil { - return err - } - - return ioutil.WriteFile(filePath, data, 0644) -} - -// loadDiffs reads the container diffs from a file -func loadDiffs(filePath string) ([]ContainerDiff, error) { - data, err := ioutil.ReadFile(filePath) - if err != nil { - return nil, err - } - - var diffs []ContainerDiff - err = json.Unmarshal(data, &diffs) - return diffs, err -} - -// profileContainers logs the current state of containers to a file -func profileContainers(cli *client.Client) { - diffs, err := getRunningContainers(cli) - if err != nil { - log.Fatalf("Error profiling containers: %v", err) - } - - for _, diff := range diffs { - if diff.Labels["oculus.mode"] == "profile" { - filePath := fmt.Sprintf("%s.json", diff.Labels["oculus.containerid"]) - err := saveDiffs([]ContainerDiff{diff}, filePath) - if err != nil { - log.Fatalf("Error saving diffs: %v", err) - } - } - } - - log.Println("Profiled current containers and saved to file") -} - -// compareContainers compares the current state of containers with the saved state -func compareContainers(cli *client.Client) { - currentDiffs, err := getRunningContainers(cli) - if err != nil { - log.Fatalf("Error getting current containers: %v", err) - } - - for _, current := range currentDiffs { - if current.Labels["oculus.mode"] == "monitor" { - filePath := fmt.Sprintf("%s.json", current.Labels["oculus.containerid"]) - savedDiffs, err := loadDiffs(filePath) - if err != nil { - log.Printf("Error loading saved diffs for %s: %v", current.ID, err) - continue - } - - if len(savedDiffs) > 0 { - saved := savedDiffs[0] - if current.Image != saved.Image { - log.Printf("Container %s changed: Image %s -> %s", current.ID, saved.Image, current.Image) - runNotifyScript(current.ID, current.Image, saved.Image) - } - if current.Labels["oculus.ignorelist"] != saved.Labels["oculus.ignorelist"] { - log.Printf("Container %s ignore list changed: %s -> %s", current.ID, saved.Labels["oculus.ignorelist"], current.Labels["oculus.ignorelist"]) - runNotifyScript(current.ID, current.Image, saved.Image) - } - } else { - log.Printf("New container detected: ID %s, Image %s", current.ID, current.Image) - runNotifyScript(current.ID, current.Image, "") - } - } - } -} - -// runNotifyScript executes the notification script with the container details -func runNotifyScript(containerID, newImage, oldImage string) { - cmd := exec.Command(notifyScript, containerID, newImage, oldImage) - err := cmd.Run() - if err != nil { - log.Printf("Error running notify script: %v", err) - } -} - -// watchDockerEvents listens for Docker events and triggers comparisons -func watchDockerEvents(cli *client.Client) { - eventFilter := filters.NewArgs() - eventFilter.Add("type", "container") - eventFilter.Add("event", "start") - eventFilter.Add("event", "stop") - eventFilter.Add("event", "destroy") - - messages, errs := cli.Events(context.Background(), types.EventsOptions{ - Filters: eventFilter, - }) - - for { - select { - case event := <-messages: - log.Printf("Docker event received: %s for container %s", event.Action, event.ID) - compareContainers(cli) - case err := <-errs: - log.Printf("Error watching Docker events: %v", err) - return - } - } -} - -// scheduler sets up the cron jobs for profiling -func scheduler(cli *client.Client) { - c := cron.New() - c.AddFunc("@every 1h", func() { profileContainers(cli) }) - c.Start() + glitchtipDSN = os.Getenv("GLITCHTIP_DSN") + if glitchtipDSN == "" { + log.Fatal("GLITCHTIP_DSN environment variable is not set") + } } func main() { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if (err != nil) { - log.Fatalf("Error creating Docker client: %v", err) - } + log.Println("Starting Oculus...") - go scheduler(cli) + // Ensure the diffs directory exists + err := os.MkdirAll("./diffs", os.ModePerm) + if err != nil { + log.Fatalf("Error creating diffs directory: %v", err) + } - go watchDockerEvents(cli) + for { + err := fetchAndMonitorContainers() + if err != nil { + log.Printf("Error in fetching and monitoring containers: %v", err) + } - // Keep the program running - select {} + time.Sleep(1 * time.Second) + } } +func fetchAndMonitorContainers() error { + containers, err := fetchContainers() + if err != nil { + return err + } + + for _, container := range containers { + mu.Lock() + if _, exists := monitoredContainers[container.ID]; !exists { + interval, err := time.ParseDuration(container.Interval) + if err != nil { + log.Printf("Invalid interval for container %s (%s): %v", container.Name, container.ID, err) + mu.Unlock() + continue + } + + monitoredContainers[container.ID] = &MonitoredContainer{ + Info: container, + LastChecked: time.Now().Add(-interval), + } + } + mu.Unlock() + } + + var wg sync.WaitGroup + for _, monitoredContainer := range monitoredContainers { + wg.Add(1) + go func(mc *MonitoredContainer) { + defer wg.Done() + interval, err := time.ParseDuration(mc.Info.Interval) + if err != nil { + log.Printf("Invalid interval for container %s (%s): %v", mc.Info.Name, mc.Info.ID, err) + return + } + + mu.Lock() + if time.Since(mc.LastChecked) >= interval { + mc.LastChecked = time.Now() + mu.Unlock() + checkAndNotify(mc.Info) + } else { + mu.Unlock() + } + }(monitoredContainer) + } + wg.Wait() + + return nil +} + +func fetchContainers() ([]ContainerInfo, error) { + resp, err := http.Get(fmt.Sprintf("%s/containers", apiAddress)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("error fetching containers: %s", resp.Status) + } + + var containers []ContainerInfo + if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { + return nil, err + } + + return containers, nil +} + +func fetchDiffOutput(containerID string) (string, error) { + resp, err := http.Get(fmt.Sprintf("%s/diff?id=%s", apiAddress, containerID)) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("error fetching diff: %s", resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} + +func checkAndNotify(container ContainerInfo) { + diffOutput, err := fetchDiffOutput(container.ID) + if err != nil { + log.Printf("Error getting diffs for container %s (%s): %v", container.Name, container.ID, err) + return + } + + filteredOutput, err := filterDiffOutput(diffOutput, container.CName, container.Ignores) + if err != nil { + log.Printf("Error filtering diffs for container %s (%s): %v", container.Name, container.ID, err) + return + } + log.Printf("Filtered diff output for container %s: %s", container.Name, filteredOutput) + + if filteredOutput != "" { + diffHash := fmt.Sprintf("%x", md5.Sum([]byte(filteredOutput))) + log.Printf("Diff hash for container %s: %s", container.Name, diffHash) + + mu.Lock() + lastNotifiedHash, notified := notifiedChanges[container.ID] + mu.Unlock() + + if !notified || lastNotifiedHash != diffHash { + filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", container.CName)) + err := writeToFile(filename, filteredOutput) + if err != nil { + log.Printf("Error writing diff to file: %v", err) + return + } + + err = sendNotification(filteredOutput) + if err != nil { + log.Printf("Error sending notification for container %s: %v", container.ID, err) + } else { + mu.Lock() + notifiedChanges[container.ID] = diffHash + mu.Unlock() + log.Printf("Notification sent and hash updated for container: %s (%s)", container.Name, container.ID) + } + } else { + log.Printf("No new changes detected for container: %s (%s)", container.Name, container.ID) + } + } else { + log.Printf("No significant changes detected for container: %s (%s)", container.Name, container.ID) + } +} + +func filterDiffOutput(diffOutput, cname string, ignores []string) (string, error) { + filename := filepath.Join("diffs", fmt.Sprintf("%s.diff", cname)) + + // Write the diff output to the file + err := ioutil.WriteFile(filename, []byte(diffOutput), 0644) + if err != nil { + return "", fmt.Errorf("error writing diff to file: %s", err) + } + + // Construct the filter command + args := append([]string{filename}, ignores...) + cmd := exec.Command("filter", args...) + fullCommand := fmt.Sprintf("filter %s", strings.Join(cmd.Args, " ")) + log.Printf("Running command: %s", fullCommand) + fmt.Printf("Running command: %s\n", fullCommand) // Print the command to stdout for debugging + + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error running filter command: %s, output: %s", err, output) + } + + // Read the filtered output + filteredOutput, err := ioutil.ReadFile(filename) + if err != nil { + return "", fmt.Errorf("error reading filtered diff file: %s", err) + } + + return string(filteredOutput), nil +} + +func writeToFile(filename, content string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + _, err = file.WriteString(content) + return err +} + +func sendNotification(content string) error { + cmd := exec.Command("go-glitch") + cmd.Stdin = strings.NewReader(content) + output, err := cmd.CombinedOutput() + if err != nil { + log.Printf("go-glitch output: %s", output) + return err + } + return nil +} diff --git a/second_container.diff b/second_container.diff new file mode 100644 index 0000000..00fa6ab --- /dev/null +++ b/second_container.diff @@ -0,0 +1,3 @@ +A /file1.txt +A /file2.txt +A /file3.txt \ No newline at end of file diff --git a/stack.production.yml b/stack.production.yml new file mode 100644 index 0000000..c036760 --- /dev/null +++ b/stack.production.yml @@ -0,0 +1,25 @@ +services: + oculus: + image: git.nixc.us/colin/oculus:production + environment: + GLITCHTIP_DSN: "" + volumes: + - /mnt/tank/persist/nixc.us/oculus/production/data:/log + - "/var/run/docker.sock:/var/run/docker.sock:ro" + deploy: + placement: + constraints: + - node.role == manager + labels: + traefik.enable: "false" + oculus.containerid: "oculus" + oculus.ignorelist: "/log/,/tmp/" + oculus.mode: "monitor" + oculus.interval: "60s" + update_config: + order: stop-first + failure_action: rollback + delay: 0s + parallelism: 1 + restart_policy: + condition: on-failure \ No newline at end of file diff --git a/stack.staging.yml b/stack.staging.yml new file mode 100644 index 0000000..9976590 --- /dev/null +++ b/stack.staging.yml @@ -0,0 +1,20 @@ +services: + oculus: + image: git.nixc.us/colin/oculus:staging + environment: + GLITCHTIP_DSN: "" + # volumes: + # - /mnt/tank/persist/nixc.us/oculus/staging/data:/log + deploy: + # placement: + # constraints: + # - node.hostname == macmini14 + labels: + traefik.enable: "false" + update_config: + order: stop-first + failure_action: rollback + delay: 0s + parallelism: 1 + restart_policy: + condition: on-failure \ No newline at end of file